Package CASPER for macOS. Fix bugs to enhance functionality and user experience.
If the application is packaged and made available for macOS, users will be able to run CASPER on their macOS computers.
If the bugs are resolved, users will be able to operate the program without experiencing crashes.
Special thanks to David for stress-testing the program last week. The following issues have been resolved:
My next step is to address any bugs that David may encounter during testing. I’ll focus on stabilizing the modules with outstanding issues and proceed with packaging the app for Windows. Additionally, I will work with David to make plans for the implementation of the Microbiome Analysis module.
|
Binary file
|
|
|
|
Binary file
|
|
|
|
Binary file
|
|
|
|
Binary file
|
|
|
|
@@ -1,61 +1,69 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
('
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
block_cipher = None
|
| 2 |
+
|
| 3 |
+
a = Analysis(['src/main.py'],
|
| 4 |
+
pathex=['src'],
|
| 5 |
+
datas=[
|
| 6 |
+
('assets', 'assets'),
|
| 7 |
+
('config', 'config'),
|
| 8 |
+
('logs', 'logs'),
|
| 9 |
+
('src', 'src'),
|
| 10 |
+
('genomeBrowserTemplate.html', '.'),
|
| 11 |
+
],
|
| 12 |
+
hiddenimports=[],
|
| 13 |
+
hookspath=[],
|
| 14 |
+
runtime_hooks=[],
|
| 15 |
+
excludes=[],
|
| 16 |
+
win_no_prefer_redirects=False,
|
| 17 |
+
win_private_assemblies=False,
|
| 18 |
+
cipher=block_cipher,
|
| 19 |
+
noarchive=False)
|
| 20 |
+
|
| 21 |
+
pyz = PYZ(a.pure, a.zipped_data,
|
| 22 |
+
cipher=block_cipher)
|
| 23 |
+
|
| 24 |
+
exe = EXE(pyz,
|
| 25 |
+
a.scripts,
|
| 26 |
+
[],
|
| 27 |
+
exclude_binaries=True,
|
| 28 |
+
name='CASPERapp',
|
| 29 |
+
debug=False,
|
| 30 |
+
bootloader_ignore_signals=False,
|
| 31 |
+
strip=False,
|
| 32 |
+
upx=True,
|
| 33 |
+
console=False,
|
| 34 |
+
disable_windowed_traceback=False,
|
| 35 |
+
target_arch=None,
|
| 36 |
+
codesign_identity=None,
|
| 37 |
+
entitlements_file=None,
|
| 38 |
+
icon='assets/CASPER_icon.icns')
|
| 39 |
+
|
| 40 |
+
coll = COLLECT(exe,
|
| 41 |
+
a.binaries,
|
| 42 |
+
a.zipfiles,
|
| 43 |
+
a.datas,
|
| 44 |
+
strip=False,
|
| 45 |
+
upx=True,
|
| 46 |
+
upx_exclude=[],
|
| 47 |
+
name='CASPERapp')
|
| 48 |
+
|
| 49 |
+
app = BUNDLE(coll,
|
| 50 |
+
name='CASPERapp.app',
|
| 51 |
+
icon='assets/CASPER_icon.icns',
|
| 52 |
+
version='2.0.1',
|
| 53 |
+
bundle_identifier=None)
|
| 54 |
+
|
| 55 |
+
# 1. Have the mac.spec in the app directory
|
| 56 |
+
# 2. pyinstaller mac.spec
|
| 57 |
+
# 3. mkdir -p dist/dmg
|
| 58 |
+
# 4. rm -r dist/dmg/*
|
| 59 |
+
# 5. Manual copy of the app into dist/dmg
|
| 60 |
+
# 6. create-dmg \
|
| 61 |
+
# --volname "CASPERapp" \
|
| 62 |
+
# --window-pos 200 120 \
|
| 63 |
+
# --window-size 600 300 \
|
| 64 |
+
# --icon-size 100 \
|
| 65 |
+
# --icon "CASPERapp.app" 175 120 \
|
| 66 |
+
# --hide-extension "CASPERapp.app" \
|
| 67 |
+
# --app-drop-link 425 120 \
|
| 68 |
+
# "dist/CASPERapp.dmg" \
|
| 69 |
+
# "dist/dmg/"
|
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from models.CoTargetingModel import CoTargetingModel
|
| 2 |
+
from views.CoTargetingView import CoTargetingView
|
| 3 |
+
|
| 4 |
+
class CoTargetingController:
|
| 5 |
+
def __init__(self, global_settings, view_targets_controller=None):
|
| 6 |
+
self.settings = global_settings
|
| 7 |
+
self.logger = global_settings.get_logger()
|
| 8 |
+
self.model = CoTargetingModel(global_settings)
|
| 9 |
+
self.view = CoTargetingView(global_settings)
|
| 10 |
+
self.view_targets_controller = view_targets_controller
|
| 11 |
+
|
| 12 |
+
# Connect signals
|
| 13 |
+
self.view.push_button_cancel.clicked.connect(self.cancel)
|
| 14 |
+
self.view.push_button_submit.clicked.connect(self.submit)
|
| 15 |
+
|
| 16 |
+
def show(self):
|
| 17 |
+
"""Show the co-targeting window"""
|
| 18 |
+
self.view.show()
|
| 19 |
+
self.view.activateWindow()
|
| 20 |
+
|
| 21 |
+
def launch(self, endo_choices, org_name):
|
| 22 |
+
"""Launch co-targeting analysis"""
|
| 23 |
+
try:
|
| 24 |
+
self.view.line_edit_organism.setText(org_name)
|
| 25 |
+
self.view.populate_table(endo_choices)
|
| 26 |
+
self.show()
|
| 27 |
+
self.view.activateWindow()
|
| 28 |
+
except Exception as e:
|
| 29 |
+
self.logger.error(f"Error launching co-targeting: {str(e)}")
|
| 30 |
+
self.view.show_error("Launch Error", str(e))
|
| 31 |
+
|
| 32 |
+
def submit(self):
|
| 33 |
+
try:
|
| 34 |
+
selected_endos = self.view.get_selected_endonucleases()
|
| 35 |
+
|
| 36 |
+
if len(selected_endos) <= 1:
|
| 37 |
+
self.view.show_error(
|
| 38 |
+
"Nothing Selected",
|
| 39 |
+
"No endonucleases selected. Please select at least 2 endonucleases"
|
| 40 |
+
)
|
| 41 |
+
return
|
| 42 |
+
|
| 43 |
+
# Validate compatibility
|
| 44 |
+
if not self.model.validate_endonucleases(selected_endos):
|
| 45 |
+
self.view.show_error(
|
| 46 |
+
"Invalid Endonucleases",
|
| 47 |
+
"The selected endonucleases are not compatible."
|
| 48 |
+
)
|
| 49 |
+
return
|
| 50 |
+
|
| 51 |
+
# Update view targets controller with selected endonucleases
|
| 52 |
+
if self.view_targets_controller:
|
| 53 |
+
self.view_targets_controller.handle_cotargeting_result(selected_endos)
|
| 54 |
+
else:
|
| 55 |
+
self.logger.error("No view_targets_controller available")
|
| 56 |
+
self.view.show_error("Error", "Could not update targets view")
|
| 57 |
+
return
|
| 58 |
+
|
| 59 |
+
self.cancel()
|
| 60 |
+
|
| 61 |
+
except Exception as e:
|
| 62 |
+
self.logger.error(f"Error in submit: {str(e)}")
|
| 63 |
+
self.view.show_error("Submit Error", str(e))
|
| 64 |
+
|
| 65 |
+
def cancel(self):
|
| 66 |
+
"""Handle cancel button click"""
|
| 67 |
+
try:
|
| 68 |
+
self.view.clear()
|
| 69 |
+
self.view.hide()
|
| 70 |
+
except Exception as e:
|
| 71 |
+
self.logger.error(f"Error in cancel: {str(e)}")
|
|
@@ -0,0 +1,223 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import platform
|
| 3 |
+
from PyQt6.QtWidgets import QFileDialog
|
| 4 |
+
from utils.ui import show_message, show_error
|
| 5 |
+
from models.ExportSelectedgRNAsModel import ExportSelectedgRNAsModel
|
| 6 |
+
from views.ExportSelectedgRNAsView import ExportSelectedgRNAsView
|
| 7 |
+
|
| 8 |
+
class ExportSelectedgRNAsController:
|
| 9 |
+
def __init__(self, global_settings):
|
| 10 |
+
self.settings = global_settings
|
| 11 |
+
self.logger = self.settings.get_logger()
|
| 12 |
+
try:
|
| 13 |
+
self.view = ExportSelectedgRNAsView(self.settings)
|
| 14 |
+
self.model = ExportSelectedgRNAsModel(self.settings)
|
| 15 |
+
self._setup_connections()
|
| 16 |
+
except Exception as e:
|
| 17 |
+
show_error(self.settings, "Error initializing ExportSelectedgRNAsController", str(e))
|
| 18 |
+
|
| 19 |
+
def _setup_connections(self) -> None:
|
| 20 |
+
try:
|
| 21 |
+
self.view.push_button_export.clicked.connect(self._handle_export)
|
| 22 |
+
self.view.push_button_cancel.clicked.connect(self._handle_cancel)
|
| 23 |
+
self.view.push_button_browse.clicked.connect(self._handle_browse)
|
| 24 |
+
except Exception as e:
|
| 25 |
+
self.logger.error(f"Error setting up connections: {str(e)}")
|
| 26 |
+
raise
|
| 27 |
+
|
| 28 |
+
def show_dialog(self, selected_items: list, window_type: str) -> None:
|
| 29 |
+
try:
|
| 30 |
+
# Reset the form fields
|
| 31 |
+
self.view.line_edit_file_name.clear()
|
| 32 |
+
self.view.line_edit_leading_sequence.clear()
|
| 33 |
+
self.view.line_edit_trailing_sequence.clear()
|
| 34 |
+
|
| 35 |
+
# Set default path
|
| 36 |
+
default_path = self.settings.get_db_path()
|
| 37 |
+
self.view.set_file_path(default_path)
|
| 38 |
+
|
| 39 |
+
# Set new data
|
| 40 |
+
self.model.set_export_data(selected_items, window_type)
|
| 41 |
+
|
| 42 |
+
# Show and bring to front
|
| 43 |
+
self.view.show()
|
| 44 |
+
self.view.raise_()
|
| 45 |
+
self.view.activateWindow()
|
| 46 |
+
|
| 47 |
+
except Exception as e:
|
| 48 |
+
show_error(self.settings, "Error showing export dialog", str(e))
|
| 49 |
+
|
| 50 |
+
def _handle_browse(self) -> None:
|
| 51 |
+
try:
|
| 52 |
+
directory = QFileDialog.getExistingDirectory(
|
| 53 |
+
self.view,
|
| 54 |
+
"Select Export Directory",
|
| 55 |
+
self.settings.CSPR_DB,
|
| 56 |
+
QFileDialog.Option.ShowDirsOnly
|
| 57 |
+
)
|
| 58 |
+
|
| 59 |
+
if directory:
|
| 60 |
+
directory += "\\" if platform.system() == "Windows" else "/"
|
| 61 |
+
self.view.set_file_path(directory)
|
| 62 |
+
except Exception as e:
|
| 63 |
+
show_error(self.settings, "Error browsing for directory", str(e))
|
| 64 |
+
|
| 65 |
+
def _handle_export(self) -> None:
|
| 66 |
+
try:
|
| 67 |
+
settings = self.view.get_export_settings()
|
| 68 |
+
|
| 69 |
+
if not settings['file_name']:
|
| 70 |
+
settings['file_name'] = "exported_gRNAs"
|
| 71 |
+
|
| 72 |
+
full_path = self.model.get_full_path(
|
| 73 |
+
settings['file_path'],
|
| 74 |
+
settings['file_name'],
|
| 75 |
+
settings['delimiter']
|
| 76 |
+
)
|
| 77 |
+
|
| 78 |
+
self._write_export_file(full_path, settings)
|
| 79 |
+
|
| 80 |
+
show_message(
|
| 81 |
+
"Export Complete",
|
| 82 |
+
f"Export to {full_path} was successful."
|
| 83 |
+
)
|
| 84 |
+
|
| 85 |
+
self._handle_cancel()
|
| 86 |
+
|
| 87 |
+
except PermissionError:
|
| 88 |
+
show_error(self.settings, "Permission Error",
|
| 89 |
+
"Cannot access the file. Please ensure it is not open elsewhere.")
|
| 90 |
+
except Exception as e:
|
| 91 |
+
show_error(self.settings, "Export Error", str(e))
|
| 92 |
+
|
| 93 |
+
def _write_export_file(self, full_path: str, settings: dict) -> None:
|
| 94 |
+
with open(full_path, 'w') as output_file:
|
| 95 |
+
delimiter = "\t" if settings['delimiter'] == r"\t" else settings['delimiter']
|
| 96 |
+
headers = self.model.get_headers()
|
| 97 |
+
output_file.write(delimiter.join(headers) + "\n")
|
| 98 |
+
self._write_data_rows(output_file, headers, settings)
|
| 99 |
+
|
| 100 |
+
def _write_data_rows(self, output_file, headers: list, settings: dict) -> None:
|
| 101 |
+
"""Write data rows to the output file"""
|
| 102 |
+
try:
|
| 103 |
+
delimiter = "\t" if settings['delimiter'] == r"\t" else settings['delimiter']
|
| 104 |
+
|
| 105 |
+
for item in self.model.data['selected_items']:
|
| 106 |
+
row_data = []
|
| 107 |
+
|
| 108 |
+
if self.model.data['window_type'] == "Multitargeting":
|
| 109 |
+
for header in headers:
|
| 110 |
+
if header == "Seed":
|
| 111 |
+
row_data.append(str(item['seed']))
|
| 112 |
+
elif header == "Total Repeats":
|
| 113 |
+
row_data.append(str(item['total_repeats']))
|
| 114 |
+
elif header == "Avg. Repeats/Scaffold":
|
| 115 |
+
row_data.append(str(item['avg_repeats_or_scaffold']))
|
| 116 |
+
elif header == "Consensus Sequence":
|
| 117 |
+
row_data.append(str(item['consensus_sequence']))
|
| 118 |
+
elif header == "Full Sequence":
|
| 119 |
+
sequence = str(item['consensus_sequence'])
|
| 120 |
+
full_sequence = (settings['leading_sequence'] +
|
| 121 |
+
sequence +
|
| 122 |
+
settings['trailing_sequence'])
|
| 123 |
+
row_data.append(full_sequence)
|
| 124 |
+
elif header == "% Consensus":
|
| 125 |
+
row_data.append(str(item['percent_consensus']))
|
| 126 |
+
elif header == "Score":
|
| 127 |
+
row_data.append(str(item['score']))
|
| 128 |
+
elif header == "PAM":
|
| 129 |
+
row_data.append(str(item['pam']))
|
| 130 |
+
elif header == "Strand":
|
| 131 |
+
row_data.append(str(item['strand']))
|
| 132 |
+
else:
|
| 133 |
+
row_data.append("")
|
| 134 |
+
|
| 135 |
+
elif self.model.data['window_type'] == "Population Analysis":
|
| 136 |
+
# Handle Population Analysis specific data format
|
| 137 |
+
for header in headers:
|
| 138 |
+
if header == "Seed":
|
| 139 |
+
row_data.append(str(item['seed']))
|
| 140 |
+
elif header == "% Coverage":
|
| 141 |
+
row_data.append(str(item['percent_coverage']))
|
| 142 |
+
elif header == "Total Repeats":
|
| 143 |
+
row_data.append(str(item['total_repeats']))
|
| 144 |
+
elif header == "Avg. Repeats/Scaffold":
|
| 145 |
+
row_data.append(str(item['avg_repeats_or_scaffold']))
|
| 146 |
+
elif header == "Consensus Sequence":
|
| 147 |
+
row_data.append(str(item['consensus_sequence']))
|
| 148 |
+
elif header == "Full Sequence":
|
| 149 |
+
sequence = str(item['consensus_sequence'])
|
| 150 |
+
full_sequence = (settings['leading_sequence'] +
|
| 151 |
+
sequence +
|
| 152 |
+
settings['trailing_sequence'])
|
| 153 |
+
row_data.append(full_sequence)
|
| 154 |
+
elif header == "% Consensus":
|
| 155 |
+
row_data.append(str(item['percent_consensus']))
|
| 156 |
+
elif header == "Score":
|
| 157 |
+
row_data.append(str(item['score']))
|
| 158 |
+
elif header == "PAM":
|
| 159 |
+
row_data.append(str(item['pam']))
|
| 160 |
+
elif header == "Strand":
|
| 161 |
+
row_data.append(str(item['strand']))
|
| 162 |
+
else:
|
| 163 |
+
row_data.append("-")
|
| 164 |
+
else:
|
| 165 |
+
# Handle View Targets window
|
| 166 |
+
for header in headers:
|
| 167 |
+
if header == "Location":
|
| 168 |
+
row_data.append(str(item['location']))
|
| 169 |
+
elif header == "Endonuclease":
|
| 170 |
+
row_data.append(str(item['endonuclease']))
|
| 171 |
+
elif header == "Sequence":
|
| 172 |
+
row_data.append(str(item['sequence']))
|
| 173 |
+
elif header == "Full Sequence":
|
| 174 |
+
sequence = str(item['sequence'])
|
| 175 |
+
full_sequence = (settings['leading_sequence'] +
|
| 176 |
+
sequence +
|
| 177 |
+
settings['trailing_sequence'])
|
| 178 |
+
row_data.append(full_sequence)
|
| 179 |
+
elif header == "Strand":
|
| 180 |
+
row_data.append(str(item['strand']))
|
| 181 |
+
elif header == "PAM":
|
| 182 |
+
row_data.append(str(item['pam']))
|
| 183 |
+
elif header == "Score":
|
| 184 |
+
row_data.append(str(item['score']))
|
| 185 |
+
elif header == "Off-Target":
|
| 186 |
+
row_data.append(str(item.get('off_target', '--.--')))
|
| 187 |
+
elif header == "Locus_Tag":
|
| 188 |
+
row_data.append(str(item.get('locus_tag', '')))
|
| 189 |
+
elif header == "Gene_Name":
|
| 190 |
+
row_data.append(str(item.get('gene_name', '')))
|
| 191 |
+
else:
|
| 192 |
+
row_data.append("")
|
| 193 |
+
|
| 194 |
+
output_file.write(delimiter.join(row_data) + "\n")
|
| 195 |
+
|
| 196 |
+
except Exception as e:
|
| 197 |
+
self.logger.error(f"Error writing data rows: {str(e)}")
|
| 198 |
+
self.logger.error(f"Data item causing error: {item}")
|
| 199 |
+
self.logger.error(f"Headers: {headers}")
|
| 200 |
+
raise
|
| 201 |
+
|
| 202 |
+
def _handle_end_of_row(self, tmp_list: list, output_file, delimiter: str) -> None:
|
| 203 |
+
"""Handle end of row processing"""
|
| 204 |
+
tmp_list.append(self.model.data['selected_items'][-1].text())
|
| 205 |
+
if self.model.data['has_locus_tag']:
|
| 206 |
+
tmp = self.settings.mainWindow.Results.comboBoxGene.currentText().split(":")
|
| 207 |
+
tmp_list.extend([tmp[0].strip(), tmp[-1].strip()])
|
| 208 |
+
elif self.model.data['has_gene_name']:
|
| 209 |
+
tmp_list.append(self.settings.mainWindow.Results.comboBoxGene.currentText().strip())
|
| 210 |
+
|
| 211 |
+
delimiter = "\t" if delimiter == r"\t" else delimiter
|
| 212 |
+
output_file.write(delimiter.join(tmp_list) + "\n")
|
| 213 |
+
|
| 214 |
+
def _handle_sequence_column(self, tmp_list: list, item, settings: dict) -> None:
|
| 215 |
+
sequence = item.get('sequence', '') if isinstance(item, dict) else item.text()
|
| 216 |
+
tmp_list.append(sequence)
|
| 217 |
+
full_sequence = (settings['leading_sequence'] +
|
| 218 |
+
sequence +
|
| 219 |
+
settings['trailing_sequence'])
|
| 220 |
+
tmp_list.append(full_sequence)
|
| 221 |
+
|
| 222 |
+
def _handle_cancel(self) -> None:
|
| 223 |
+
self.view.hide()
|
|
@@ -1,4 +1,5 @@
|
|
| 1 |
from models.FindTargetsModel import FindTargetsModel
|
|
|
|
| 2 |
from views.FindTargetsView import FindTargetsView
|
| 3 |
from PyQt6.QtWidgets import QMessageBox
|
| 4 |
from PyQt6.QtCore import QTimer
|
|
@@ -103,20 +104,84 @@ class FindTargetsController:
|
|
| 103 |
try:
|
| 104 |
if not self.view:
|
| 105 |
return
|
| 106 |
-
|
| 107 |
selected_targets = self.view.get_selected_targets()
|
| 108 |
-
print(f"Selected targets: {selected_targets}")
|
| 109 |
-
print(f"Organism: {self.organism}")
|
| 110 |
-
print(f"Endonuclease: {self.endonuclease}")
|
| 111 |
if not selected_targets:
|
| 112 |
QMessageBox.warning(self.view, "No Selection", "Please select targets to view.")
|
| 113 |
return
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
except Exception as e:
|
| 120 |
self.global_settings.logger.error(f"Error in view_targets: {str(e)}")
|
| 121 |
if self.view:
|
| 122 |
QMessageBox.critical(self.view, "Error", f"An error occurred while viewing targets: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from models.FindTargetsModel import FindTargetsModel
|
| 2 |
+
from utils.ui import show_error
|
| 3 |
from views.FindTargetsView import FindTargetsView
|
| 4 |
from PyQt6.QtWidgets import QMessageBox
|
| 5 |
from PyQt6.QtCore import QTimer
|
|
|
|
| 104 |
try:
|
| 105 |
if not self.view:
|
| 106 |
return
|
| 107 |
+
|
| 108 |
selected_targets = self.view.get_selected_targets()
|
|
|
|
|
|
|
|
|
|
| 109 |
if not selected_targets:
|
| 110 |
QMessageBox.warning(self.view, "No Selection", "Please select targets to view.")
|
| 111 |
return
|
| 112 |
+
|
| 113 |
+
# Find existing View Targets tab
|
| 114 |
+
main_window = self.global_settings.main_window
|
| 115 |
+
existing_tab = main_window.find_tab_by_title("View Targets")
|
| 116 |
|
| 117 |
+
if existing_tab:
|
| 118 |
+
# Get the existing controller from main window's tab_widgets
|
| 119 |
+
view_targets_controller = main_window.tab_widgets['controllers'].get("View Targets")
|
| 120 |
+
if view_targets_controller:
|
| 121 |
+
# Update existing view with new targets
|
| 122 |
+
view_targets_controller.load_targets(selected_targets, self.organism, self.endonuclease)
|
| 123 |
+
# Switch to the existing tab
|
| 124 |
+
main_window.view.tab_widget.setCurrentWidget(existing_tab)
|
| 125 |
+
else:
|
| 126 |
+
self.logger.error("View Targets controller not found for existing tab")
|
| 127 |
+
else:
|
| 128 |
+
# Create new View Targets tab if none exists
|
| 129 |
+
view_targets_controller = self.global_settings.get_view_targets_window()
|
| 130 |
+
view_targets_controller.load_guides(selected_targets, self.organism, self.endonuclease)
|
| 131 |
+
main_window.open_new_tab("View Targets", view_targets_controller)
|
| 132 |
+
|
| 133 |
except Exception as e:
|
| 134 |
self.global_settings.logger.error(f"Error in view_targets: {str(e)}")
|
| 135 |
if self.view:
|
| 136 |
QMessageBox.critical(self.view, "Error", f"An error occurred while viewing targets: {str(e)}")
|
| 137 |
+
|
| 138 |
+
def gather_settings(self):
|
| 139 |
+
"""Process input data and direct to appropriate view"""
|
| 140 |
+
try:
|
| 141 |
+
input_data = self.view.get_find_targets_input()
|
| 142 |
+
|
| 143 |
+
# For position-based searches, go directly to view targets
|
| 144 |
+
if input_data['search_type'] == 'position':
|
| 145 |
+
self.open_view_targets_directly(input_data)
|
| 146 |
+
else:
|
| 147 |
+
# For other search types, show find targets view first
|
| 148 |
+
self.find_targets(input_data)
|
| 149 |
+
|
| 150 |
+
except Exception as e:
|
| 151 |
+
show_error(self.global_settings, "Error in find_targets", str(e))
|
| 152 |
+
|
| 153 |
+
def open_view_targets_directly(self, input_data):
|
| 154 |
+
"""Open view targets directly for position-based searches"""
|
| 155 |
+
try:
|
| 156 |
+
# Get targets using the model
|
| 157 |
+
targets = self.model.find_targets_by_position(
|
| 158 |
+
self.model._get_parser(self.model.get_cspr_file_path(input_data)),
|
| 159 |
+
input_data
|
| 160 |
+
)
|
| 161 |
+
|
| 162 |
+
if targets:
|
| 163 |
+
# Create view targets controller
|
| 164 |
+
view_targets_controller = self.global_settings.get_view_targets_window()
|
| 165 |
+
|
| 166 |
+
# Load targets directly
|
| 167 |
+
view_targets_controller.load_targets(
|
| 168 |
+
targets,
|
| 169 |
+
input_data['organism'],
|
| 170 |
+
input_data['endonuclease']
|
| 171 |
+
)
|
| 172 |
+
|
| 173 |
+
# Open view targets tab
|
| 174 |
+
self.global_settings.main_window.open_new_tab(
|
| 175 |
+
"View Targets",
|
| 176 |
+
view_targets_controller
|
| 177 |
+
)
|
| 178 |
+
else:
|
| 179 |
+
QMessageBox.warning(
|
| 180 |
+
self.view,
|
| 181 |
+
"No Targets Found",
|
| 182 |
+
"No targets were found in the specified position range."
|
| 183 |
+
)
|
| 184 |
+
|
| 185 |
+
except Exception as e:
|
| 186 |
+
self.global_settings.logger.error(f"Error opening view targets directly: {str(e)}")
|
| 187 |
+
show_error(self.global_settings, "Error", f"Could not open view targets: {str(e)}")
|
|
@@ -0,0 +1,229 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from views.GenerateLibraryView import GenerateLibraryView
|
| 2 |
+
from models.GenerateLibraryModel import GenerateLibraryModel
|
| 3 |
+
from PyQt6.QtCore import QObject
|
| 4 |
+
import os
|
| 5 |
+
|
| 6 |
+
class GenerateLibraryController(QObject):
|
| 7 |
+
def __init__(self, global_settings, selected_targets=None):
|
| 8 |
+
# Initialize QObject first
|
| 9 |
+
super(GenerateLibraryController, self).__init__()
|
| 10 |
+
|
| 11 |
+
self.global_settings = global_settings
|
| 12 |
+
self.logger = global_settings.logger
|
| 13 |
+
self.model = GenerateLibraryModel(global_settings)
|
| 14 |
+
self.view = GenerateLibraryView(global_settings)
|
| 15 |
+
|
| 16 |
+
# Get CSPR file path
|
| 17 |
+
if selected_targets and len(selected_targets) > 0:
|
| 18 |
+
# Get organism name from the first target's chromosome
|
| 19 |
+
chrom = selected_targets[0].get('full_chromosome', '')
|
| 20 |
+
if chrom:
|
| 21 |
+
# Extract organism name from chromosome ID
|
| 22 |
+
org_name = chrom.split('.')[0]
|
| 23 |
+
default_filename = f"{org_name}_lib.csv"
|
| 24 |
+
self.view.ledFileName.setText(default_filename)
|
| 25 |
+
|
| 26 |
+
# Get CSPR file path and initialize parser
|
| 27 |
+
org_files = self.model.get_organism_to_files()
|
| 28 |
+
endonuclease = selected_targets[0].get('endonuclease', '').lower()
|
| 29 |
+
if org_name in org_files and endonuclease in org_files[org_name]:
|
| 30 |
+
cspr_file = os.path.join(
|
| 31 |
+
self.global_settings.get_db_path(),
|
| 32 |
+
org_files[org_name][endonuclease][0]
|
| 33 |
+
)
|
| 34 |
+
self.model.initialize_parser(cspr_file)
|
| 35 |
+
|
| 36 |
+
# Get guide data for each target
|
| 37 |
+
processed_targets = []
|
| 38 |
+
for target in selected_targets:
|
| 39 |
+
target_info = [{
|
| 40 |
+
'feature_id': target['feature_id'],
|
| 41 |
+
'feature_name': target['feature_name'],
|
| 42 |
+
'start': target['start'],
|
| 43 |
+
'end': target['end'],
|
| 44 |
+
'chromosome': target['chromosome']
|
| 45 |
+
}]
|
| 46 |
+
guides = self.model.parser.read_targets_batch(
|
| 47 |
+
target['chromosome'],
|
| 48 |
+
target_info,
|
| 49 |
+
target['endonuclease']
|
| 50 |
+
)
|
| 51 |
+
if guides:
|
| 52 |
+
for guide in guides:
|
| 53 |
+
guide.update({
|
| 54 |
+
'feature_id': target['feature_id'],
|
| 55 |
+
'feature_name': target['feature_name'],
|
| 56 |
+
'feature_type': target['feature_type'],
|
| 57 |
+
'feature_description': target['feature_description'],
|
| 58 |
+
'start': target['start'],
|
| 59 |
+
'end': target['end']
|
| 60 |
+
})
|
| 61 |
+
processed_targets.extend(guides)
|
| 62 |
+
|
| 63 |
+
self.selected_targets = processed_targets
|
| 64 |
+
else:
|
| 65 |
+
self.selected_targets = selected_targets
|
| 66 |
+
else:
|
| 67 |
+
self.selected_targets = selected_targets
|
| 68 |
+
|
| 69 |
+
# Log initialization
|
| 70 |
+
self.logger.debug(f"Initializing GenerateLibraryController with {len(selected_targets) if selected_targets else 0} targets")
|
| 71 |
+
|
| 72 |
+
self._connect_signals()
|
| 73 |
+
|
| 74 |
+
def _connect_signals(self):
|
| 75 |
+
"""Connect view signals to controller methods"""
|
| 76 |
+
try:
|
| 77 |
+
self.view.submit_clicked.connect(self._handle_submit)
|
| 78 |
+
self.logger.debug("Connected GenerateLibraryView signals")
|
| 79 |
+
except Exception as e:
|
| 80 |
+
self.logger.error(f"Error connecting signals: {str(e)}")
|
| 81 |
+
|
| 82 |
+
def show(self):
|
| 83 |
+
"""Show the generate library window"""
|
| 84 |
+
try:
|
| 85 |
+
self.logger.debug("Showing GenerateLibraryView")
|
| 86 |
+
|
| 87 |
+
# Store reference to prevent garbage collection
|
| 88 |
+
self.global_settings.main_window._current_generate_library_controller = self
|
| 89 |
+
|
| 90 |
+
# Show the view
|
| 91 |
+
self.view.show()
|
| 92 |
+
|
| 93 |
+
except Exception as e:
|
| 94 |
+
self.logger.error(f"Error showing generate library window: {str(e)}")
|
| 95 |
+
|
| 96 |
+
def _handle_submit(self, settings):
|
| 97 |
+
"""Handle submit button click"""
|
| 98 |
+
try:
|
| 99 |
+
self.logger.debug(f"Handling submit with settings: {settings}")
|
| 100 |
+
|
| 101 |
+
# Validate settings
|
| 102 |
+
self._validate_settings(settings)
|
| 103 |
+
|
| 104 |
+
# Get guide data for targets if not already processed
|
| 105 |
+
if not hasattr(self, 'processed_targets'):
|
| 106 |
+
self.processed_targets = []
|
| 107 |
+
|
| 108 |
+
# Get CSPR file path
|
| 109 |
+
if self.selected_targets and len(self.selected_targets) > 0:
|
| 110 |
+
first_target = self.selected_targets[0]
|
| 111 |
+
self.logger.debug(f"First target: {first_target}")
|
| 112 |
+
|
| 113 |
+
# Get organism name from the main window's home window view
|
| 114 |
+
home_window = self.global_settings._current_home_window
|
| 115 |
+
org_name = home_window.view.combo_box_organism.currentText()
|
| 116 |
+
endonuclease = first_target['endonuclease'].lower()
|
| 117 |
+
|
| 118 |
+
self.logger.debug(f"Looking for CSPR file for {org_name} and {endonuclease}")
|
| 119 |
+
|
| 120 |
+
# Get CSPR file path
|
| 121 |
+
org_files = self.model.get_organism_to_files()
|
| 122 |
+
if org_name in org_files:
|
| 123 |
+
# Debug available endonucleases
|
| 124 |
+
self.logger.debug(f"Available endonucleases for {org_name}: {list(org_files[org_name].keys())}")
|
| 125 |
+
|
| 126 |
+
# Try both lowercase and original case
|
| 127 |
+
cspr_file = None
|
| 128 |
+
if endonuclease in org_files[org_name]:
|
| 129 |
+
cspr_file = org_files[org_name][endonuclease][0]
|
| 130 |
+
elif first_target['endonuclease'] in org_files[org_name]:
|
| 131 |
+
cspr_file = org_files[org_name][first_target['endonuclease']][0]
|
| 132 |
+
|
| 133 |
+
if cspr_file:
|
| 134 |
+
cspr_path = os.path.join(self.global_settings.get_db_path(), cspr_file)
|
| 135 |
+
self.logger.debug(f"Using CSPR file: {cspr_path}")
|
| 136 |
+
|
| 137 |
+
self.model.initialize_parser(cspr_path)
|
| 138 |
+
|
| 139 |
+
# Process each target to get guide data
|
| 140 |
+
for target in self.selected_targets:
|
| 141 |
+
# Get start and end from location if not present
|
| 142 |
+
if 'start' not in target or 'end' not in target:
|
| 143 |
+
if 'location' in target:
|
| 144 |
+
start, end = map(int, target['location'].split('-'))
|
| 145 |
+
target['start'] = start
|
| 146 |
+
target['end'] = end
|
| 147 |
+
|
| 148 |
+
target_info = [{
|
| 149 |
+
'feature_id': target['feature_id'],
|
| 150 |
+
'feature_name': target['feature_name'],
|
| 151 |
+
'start': int(target['start']),
|
| 152 |
+
'end': int(target['end']),
|
| 153 |
+
'chromosome': target['chromosome']
|
| 154 |
+
}]
|
| 155 |
+
|
| 156 |
+
self.logger.debug(f"Searching guides for target: {target_info}")
|
| 157 |
+
|
| 158 |
+
# Get guides for this target
|
| 159 |
+
guides = self.model.parser.read_targets_batch(
|
| 160 |
+
target['chromosome'],
|
| 161 |
+
target_info,
|
| 162 |
+
target['endonuclease']
|
| 163 |
+
)
|
| 164 |
+
|
| 165 |
+
if guides:
|
| 166 |
+
self.logger.debug(f"Found {len(guides)} guides for target {target['feature_id']}")
|
| 167 |
+
# Add target info to each guide
|
| 168 |
+
for guide in guides:
|
| 169 |
+
guide.update({
|
| 170 |
+
'feature_id': target['feature_id'],
|
| 171 |
+
'feature_name': target['feature_name'],
|
| 172 |
+
'feature_type': target.get('feature_type', 'CDS'),
|
| 173 |
+
'feature_description': target.get('feature_description', ''),
|
| 174 |
+
'start': int(target['start']),
|
| 175 |
+
'end': int(target['end'])
|
| 176 |
+
})
|
| 177 |
+
self.processed_targets.extend(guides)
|
| 178 |
+
else:
|
| 179 |
+
self.logger.warning(f"No guides found for target {target['feature_id']}")
|
| 180 |
+
|
| 181 |
+
self.logger.debug(f"Processed {len(self.processed_targets)} total guides from CSPR file")
|
| 182 |
+
else:
|
| 183 |
+
raise ValueError(f"Could not find CSPR file for endonuclease {endonuclease}")
|
| 184 |
+
else:
|
| 185 |
+
raise ValueError(f"Could not find organism {org_name} in database")
|
| 186 |
+
|
| 187 |
+
# Generate library using processed targets
|
| 188 |
+
success = self.model.generate_library(
|
| 189 |
+
self.processed_targets if hasattr(self, 'processed_targets') else self.selected_targets,
|
| 190 |
+
settings
|
| 191 |
+
)
|
| 192 |
+
|
| 193 |
+
if success:
|
| 194 |
+
self.view.show_success("Library generated successfully!")
|
| 195 |
+
self.view.close()
|
| 196 |
+
|
| 197 |
+
except ValueError as e:
|
| 198 |
+
self.logger.error(f"Validation error: {str(e)}")
|
| 199 |
+
self.view.show_error("Invalid Input", str(e))
|
| 200 |
+
except Exception as e:
|
| 201 |
+
self.logger.error(f"Error generating library: {str(e)}")
|
| 202 |
+
self.view.show_error(
|
| 203 |
+
"Error",
|
| 204 |
+
f"An error occurred while generating the library: {str(e)}"
|
| 205 |
+
)
|
| 206 |
+
|
| 207 |
+
def _validate_settings(self, settings):
|
| 208 |
+
"""Validate library generation settings"""
|
| 209 |
+
try:
|
| 210 |
+
if not settings['output_file']:
|
| 211 |
+
raise ValueError("Please specify an output file")
|
| 212 |
+
|
| 213 |
+
if settings['target_range_start'] >= settings['target_range_end']:
|
| 214 |
+
raise ValueError("Start range must be less than end range")
|
| 215 |
+
|
| 216 |
+
if settings['target_range_start'] < 0 or settings['target_range_end'] > 100:
|
| 217 |
+
raise ValueError("Target range must be between 0 and 100")
|
| 218 |
+
|
| 219 |
+
if settings['space_between_guides'] < 0:
|
| 220 |
+
raise ValueError("Space between guides must be positive")
|
| 221 |
+
|
| 222 |
+
if settings.get('find_off_targets'):
|
| 223 |
+
max_score = settings.get('max_off_target_score')
|
| 224 |
+
if max_score is None or not 0 < max_score <= 0.5:
|
| 225 |
+
raise ValueError("Maximum off-target score must be between 0 and 0.5")
|
| 226 |
+
|
| 227 |
+
except Exception as e:
|
| 228 |
+
self.logger.error(f"Settings validation error: {str(e)}")
|
| 229 |
+
raise
|
|
@@ -1,11 +1,12 @@
|
|
| 1 |
import os
|
| 2 |
from PyQt6 import QtWidgets, QtCore, uic
|
| 3 |
-
from PyQt6.QtWidgets import QMainWindow
|
| 4 |
from views.HomeWindowView import HomeWindowView
|
| 5 |
from models.HomeWindowModel import HomeWindowModel
|
| 6 |
from utils.ui import show_error, show_message
|
| 7 |
from PyQt6.QtCore import QObject
|
| 8 |
from controllers.FindTargetsController import FindTargetsController
|
|
|
|
| 9 |
|
| 10 |
class HomeWindowController:
|
| 11 |
def __init__(self, global_settings):
|
|
@@ -16,41 +17,39 @@ class HomeWindowController:
|
|
| 16 |
self.view = HomeWindowView(global_settings)
|
| 17 |
self.init_ui()
|
| 18 |
self.setup_connections()
|
| 19 |
-
self.global_settings.db_state_updated.connect(self.refresh_data)
|
| 20 |
-
|
| 21 |
-
main_window = self.global_settings.main_window
|
| 22 |
-
|
| 23 |
-
# Ensure the model data is loaded
|
| 24 |
self.model.load_data()
|
| 25 |
-
|
|
|
|
|
|
|
| 26 |
except Exception as e:
|
| 27 |
show_error(self.global_settings, "Error initializing HomeWindowController", str(e))
|
| 28 |
|
| 29 |
def init_ui(self):
|
| 30 |
try:
|
| 31 |
-
self.view.push_button_view_targets.setEnabled(False)
|
| 32 |
-
self.view.push_button_generate_library.setEnabled(False)
|
| 33 |
self.load_combo_box_data()
|
| 34 |
-
self.
|
| 35 |
except Exception as e:
|
| 36 |
show_error(self.global_settings, "Error initializing UI in HomeWindowController", str(e))
|
| 37 |
|
| 38 |
def load_combo_box_data(self):
|
|
|
|
| 39 |
try:
|
| 40 |
self.model.load_data()
|
| 41 |
-
|
| 42 |
organism_to_endonuclease = self.model.get_organism_to_endonuclease()
|
| 43 |
annotation_files = self.model.get_annotation_files()
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
self.view.update_combo_box_organism(
|
| 47 |
-
|
|
|
|
| 48 |
self.update_combo_box_endonuclease()
|
| 49 |
-
|
| 50 |
-
|
| 51 |
self.view.update_combo_box_annotation_files(annotation_files)
|
|
|
|
| 52 |
except Exception as e:
|
| 53 |
-
show_error(self.global_settings, "Error loading dropdown data
|
| 54 |
|
| 55 |
def update_combo_box_endonuclease(self):
|
| 56 |
selected_organism = self.view.combo_box_organism.currentText()
|
|
@@ -73,11 +72,12 @@ class HomeWindowController:
|
|
| 73 |
self.view.push_button_ncbi_file_search.clicked.connect(self.open_ncbi_window)
|
| 74 |
|
| 75 |
# grpStep3
|
| 76 |
-
self.view.radio_button_feature.clicked.connect(self.toggle_annotation)
|
| 77 |
-
self.view.radio_button_position.clicked.connect(self.toggle_annotation)
|
| 78 |
-
self.view.
|
| 79 |
-
self.view.
|
| 80 |
-
self.view.
|
|
|
|
| 81 |
|
| 82 |
# Add connection for annotation file changes
|
| 83 |
self.view.combo_box_local_annotation_files.currentTextChanged.connect(self._on_annotation_file_changed)
|
|
@@ -87,27 +87,99 @@ class HomeWindowController:
|
|
| 87 |
|
| 88 |
# Event Handlers
|
| 89 |
def gather_settings(self):
|
|
|
|
| 90 |
try:
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
|
| 95 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
except Exception as e:
|
| 97 |
-
|
|
|
|
| 98 |
|
| 99 |
-
def
|
| 100 |
-
|
| 101 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
|
| 103 |
def toggle_annotation(self):
|
| 104 |
# Implementation for toggling annotation
|
| 105 |
pass
|
| 106 |
|
| 107 |
-
def prep_gen_lib(self):
|
| 108 |
-
# Implementation for preparing gene library
|
| 109 |
-
pass
|
| 110 |
-
|
| 111 |
def open_new_genome_module(self):
|
| 112 |
try:
|
| 113 |
main_window = self.global_settings.main_window
|
|
@@ -172,33 +244,51 @@ class HomeWindowController:
|
|
| 172 |
except Exception as e:
|
| 173 |
show_error(self.global_settings, "Error in open_ncbi_window() in main", str(e))
|
| 174 |
|
| 175 |
-
def
|
|
|
|
| 176 |
try:
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 181 |
except Exception as e:
|
| 182 |
-
show_error(self.global_settings, "Error
|
| 183 |
|
| 184 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
try:
|
| 186 |
-
|
| 187 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
except Exception as e:
|
| 189 |
-
show_error(self.global_settings, "Error
|
| 190 |
-
|
| 191 |
-
def refresh_data(self, is_valid, message, cspr_files):
|
| 192 |
-
self.logger.info(f"Refreshing Home Window data after database state update. Valid: {is_valid}, Message: {message}")
|
| 193 |
-
if is_valid:
|
| 194 |
-
self.load_combo_box_data()
|
| 195 |
-
# If the current tab is not Home, we need to update it when it becomes visible
|
| 196 |
-
main_window = self.global_settings.main_window
|
| 197 |
-
current_tab_text = main_window.view.tab_widget.tabText(main_window.view.tab_widget.currentIndex())
|
| 198 |
-
if current_tab_text != "Home":
|
| 199 |
-
main_window.view.tab_widget.currentChanged.connect(self._check_and_update_home_tab)
|
| 200 |
-
else:
|
| 201 |
-
self.logger.warning(f"Database state update received, but it's not valid. Message: {message}")
|
| 202 |
|
| 203 |
def _check_and_update_home_tab(self, index):
|
| 204 |
if self.global_settings.main_window.view.tab_widget.tabText(index) == "Home":
|
|
@@ -219,3 +309,29 @@ class HomeWindowController:
|
|
| 219 |
"""Handle changes to the annotation file selection"""
|
| 220 |
self.global_settings.set_current_annotation_file(new_file)
|
| 221 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import os
|
| 2 |
from PyQt6 import QtWidgets, QtCore, uic
|
| 3 |
+
from PyQt6.QtWidgets import QMainWindow, QMessageBox
|
| 4 |
from views.HomeWindowView import HomeWindowView
|
| 5 |
from models.HomeWindowModel import HomeWindowModel
|
| 6 |
from utils.ui import show_error, show_message
|
| 7 |
from PyQt6.QtCore import QObject
|
| 8 |
from controllers.FindTargetsController import FindTargetsController
|
| 9 |
+
from models.DatabaseManager import FileChangeType
|
| 10 |
|
| 11 |
class HomeWindowController:
|
| 12 |
def __init__(self, global_settings):
|
|
|
|
| 17 |
self.view = HomeWindowView(global_settings)
|
| 18 |
self.init_ui()
|
| 19 |
self.setup_connections()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
self.model.load_data()
|
| 21 |
+
self.global_settings.db_manager.db_files_changed.connect(self._handle_db_files_changed)
|
| 22 |
+
self.global_settings.db_manager.db_validation_changed.connect(self._handle_db_validation_changed)
|
| 23 |
+
self.global_settings.db_manager.db_state_changed.connect(self._handle_db_state_changed)
|
| 24 |
except Exception as e:
|
| 25 |
show_error(self.global_settings, "Error initializing HomeWindowController", str(e))
|
| 26 |
|
| 27 |
def init_ui(self):
|
| 28 |
try:
|
|
|
|
|
|
|
| 29 |
self.load_combo_box_data()
|
| 30 |
+
self.handle_search_type_change()
|
| 31 |
except Exception as e:
|
| 32 |
show_error(self.global_settings, "Error initializing UI in HomeWindowController", str(e))
|
| 33 |
|
| 34 |
def load_combo_box_data(self):
|
| 35 |
+
"""Reload all combo box data"""
|
| 36 |
try:
|
| 37 |
self.model.load_data()
|
| 38 |
+
|
| 39 |
organism_to_endonuclease = self.model.get_organism_to_endonuclease()
|
| 40 |
annotation_files = self.model.get_annotation_files()
|
| 41 |
+
|
| 42 |
+
# Update organisms combo box
|
| 43 |
+
self.view.update_combo_box_organism(sorted(organism_to_endonuclease.keys()))
|
| 44 |
+
|
| 45 |
+
# Update endonuclease combo box
|
| 46 |
self.update_combo_box_endonuclease()
|
| 47 |
+
|
| 48 |
+
# Update annotation files combo box
|
| 49 |
self.view.update_combo_box_annotation_files(annotation_files)
|
| 50 |
+
|
| 51 |
except Exception as e:
|
| 52 |
+
show_error(self.global_settings, "Error loading dropdown data", str(e))
|
| 53 |
|
| 54 |
def update_combo_box_endonuclease(self):
|
| 55 |
selected_organism = self.view.combo_box_organism.currentText()
|
|
|
|
| 72 |
self.view.push_button_ncbi_file_search.clicked.connect(self.open_ncbi_window)
|
| 73 |
|
| 74 |
# grpStep3
|
| 75 |
+
# self.view.radio_button_feature.clicked.connect(self.toggle_annotation)
|
| 76 |
+
# self.view.radio_button_position.clicked.connect(self.toggle_annotation)
|
| 77 |
+
self.view.radio_button_feature.clicked.connect(self.handle_search_type_change)
|
| 78 |
+
self.view.radio_button_position.clicked.connect(self.handle_search_type_change)
|
| 79 |
+
self.view.radio_button_sequence.clicked.connect(self.handle_search_type_change)
|
| 80 |
+
self.view.push_button_find_view_targets.clicked.connect(self.gather_settings)
|
| 81 |
|
| 82 |
# Add connection for annotation file changes
|
| 83 |
self.view.combo_box_local_annotation_files.currentTextChanged.connect(self._on_annotation_file_changed)
|
|
|
|
| 87 |
|
| 88 |
# Event Handlers
|
| 89 |
def gather_settings(self):
|
| 90 |
+
"""Process input data and direct to appropriate view"""
|
| 91 |
try:
|
| 92 |
+
input_data = self.view.get_find_targets_input()
|
| 93 |
+
|
| 94 |
+
if input_data['search_type'] == 'sequence':
|
| 95 |
+
sequence = input_data['search_query'].strip()
|
| 96 |
+
if len(sequence) < 100:
|
| 97 |
+
QMessageBox.warning(
|
| 98 |
+
self.view,
|
| 99 |
+
"Sequence Too Short",
|
| 100 |
+
"The sequence given is too small. At least 100 characters are required."
|
| 101 |
+
)
|
| 102 |
+
return
|
| 103 |
+
self.open_view_targets(input_data)
|
| 104 |
+
elif input_data['search_type'] == 'position':
|
| 105 |
+
self.open_view_targets(input_data)
|
| 106 |
+
else:
|
| 107 |
+
self.open_find_targets_module()
|
| 108 |
+
|
| 109 |
+
except Exception as e:
|
| 110 |
+
show_error(self.global_settings, "Error in gather_settings", str(e))
|
| 111 |
|
| 112 |
+
def open_view_targets(self, input_data):
|
| 113 |
+
try:
|
| 114 |
+
# Create find targets controller to use its model
|
| 115 |
+
find_targets_controller = self.global_settings.get_find_targets_window()
|
| 116 |
+
|
| 117 |
+
# Get targets using the model
|
| 118 |
+
targets = find_targets_controller.model.find_targets(input_data)
|
| 119 |
+
|
| 120 |
+
if targets:
|
| 121 |
+
self.logger.debug(f"Found {len(targets)} targets")
|
| 122 |
+
|
| 123 |
+
# Close existing View Targets tab if it exists
|
| 124 |
+
main_window = self.global_settings.main_window
|
| 125 |
+
existing_tab = main_window.find_tab_by_title("View Targets")
|
| 126 |
+
if existing_tab:
|
| 127 |
+
tab_index = main_window.view.tab_widget.indexOf(existing_tab)
|
| 128 |
+
main_window._close_tab(tab_index)
|
| 129 |
+
self.logger.debug("Closed existing View Targets tab")
|
| 130 |
+
|
| 131 |
+
# Create view targets controller
|
| 132 |
+
view_targets_controller = self.global_settings.get_view_targets_window()
|
| 133 |
+
|
| 134 |
+
view_targets_controller.load_guides(
|
| 135 |
+
targets, # Pass the targets directly
|
| 136 |
+
input_data['organism'],
|
| 137 |
+
input_data['endonuclease']
|
| 138 |
+
)
|
| 139 |
+
|
| 140 |
+
# Open new view targets tab
|
| 141 |
+
main_window.open_new_tab(
|
| 142 |
+
"View Targets",
|
| 143 |
+
view_targets_controller
|
| 144 |
+
)
|
| 145 |
+
|
| 146 |
+
else:
|
| 147 |
+
QMessageBox.warning(
|
| 148 |
+
self.view,
|
| 149 |
+
"No Targets Found",
|
| 150 |
+
"No targets were found for the specified search."
|
| 151 |
+
)
|
| 152 |
+
|
| 153 |
except Exception as e:
|
| 154 |
+
self.global_settings.logger.error(f"Error opening view targets directly: {str(e)}")
|
| 155 |
+
show_error(self.global_settings, "Error", f"Could not open view targets: {str(e)}")
|
| 156 |
|
| 157 |
+
def open_find_targets_module(self):
|
| 158 |
+
"""Open find targets module for non-position searches"""
|
| 159 |
+
try:
|
| 160 |
+
# Close existing Find Targets tab if it exists
|
| 161 |
+
main_window = self.global_settings.main_window
|
| 162 |
+
existing_tab = main_window.find_tab_by_title("Find Targets")
|
| 163 |
+
if existing_tab:
|
| 164 |
+
tab_index = main_window.view.tab_widget.indexOf(existing_tab)
|
| 165 |
+
main_window._close_tab(tab_index)
|
| 166 |
+
self.logger.debug("Closed existing Find Targets tab")
|
| 167 |
+
|
| 168 |
+
# Create new find targets controller and load data
|
| 169 |
+
find_targets_controller = self.global_settings.get_find_targets_window()
|
| 170 |
+
input_data = self.view.get_find_targets_input()
|
| 171 |
+
find_targets_controller.find_targets(input_data)
|
| 172 |
+
|
| 173 |
+
# Open new Find Targets tab
|
| 174 |
+
self.global_settings.main_window.open_new_tab("Find Targets", find_targets_controller)
|
| 175 |
+
|
| 176 |
+
except Exception as e:
|
| 177 |
+
show_error(self.global_settings, "Error in open_find_targets_module() in Home", str(e))
|
| 178 |
|
| 179 |
def toggle_annotation(self):
|
| 180 |
# Implementation for toggling annotation
|
| 181 |
pass
|
| 182 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
def open_new_genome_module(self):
|
| 184 |
try:
|
| 185 |
main_window = self.global_settings.main_window
|
|
|
|
| 244 |
except Exception as e:
|
| 245 |
show_error(self.global_settings, "Error in open_ncbi_window() in main", str(e))
|
| 246 |
|
| 247 |
+
def _handle_db_files_changed(self, changes):
|
| 248 |
+
"""Handle database file changes"""
|
| 249 |
try:
|
| 250 |
+
# Reload model data if necessary
|
| 251 |
+
self.model.update_for_file_changes(changes)
|
| 252 |
+
|
| 253 |
+
# Update UI if needed
|
| 254 |
+
if (FileChangeType.CSPR_ADDED in changes or
|
| 255 |
+
FileChangeType.CSPR_REMOVED in changes):
|
| 256 |
+
# Update both organism and endonuclease combo boxes
|
| 257 |
+
organism_to_endonuclease = self.model.get_organism_to_endonuclease()
|
| 258 |
+
self.view.update_combo_box_organism(sorted(organism_to_endonuclease.keys()))
|
| 259 |
+
self.update_combo_box_endonuclease()
|
| 260 |
+
|
| 261 |
+
if (FileChangeType.GBFF_ADDED in changes or
|
| 262 |
+
FileChangeType.GBFF_REMOVED in changes):
|
| 263 |
+
self.view.update_combo_box_annotation_files(self.model.get_annotation_files())
|
| 264 |
+
|
| 265 |
except Exception as e:
|
| 266 |
+
show_error(self.global_settings, "Error handling database changes", str(e))
|
| 267 |
|
| 268 |
+
def _handle_db_validation_changed(self, is_valid, message):
|
| 269 |
+
"""Handle database validation state changes"""
|
| 270 |
+
if not is_valid:
|
| 271 |
+
self.view.show_warning("Database Warning", message)
|
| 272 |
+
self._update_validation_state(is_valid)
|
| 273 |
+
|
| 274 |
+
def _handle_db_state_changed(self, is_valid, message, changes):
|
| 275 |
+
"""Handle database state changes"""
|
| 276 |
try:
|
| 277 |
+
if not is_valid:
|
| 278 |
+
show_error(self.global_settings, "Database Warning", message)
|
| 279 |
+
return
|
| 280 |
+
|
| 281 |
+
# Always reload model data when database state changes
|
| 282 |
+
self.model.load_data()
|
| 283 |
+
|
| 284 |
+
# Update all combo boxes
|
| 285 |
+
organism_to_endonuclease = self.model.get_organism_to_endonuclease()
|
| 286 |
+
self.view.update_combo_box_organism(sorted(organism_to_endonuclease.keys()))
|
| 287 |
+
self.update_combo_box_endonuclease()
|
| 288 |
+
self.view.update_combo_box_annotation_files(self.model.get_annotation_files())
|
| 289 |
+
|
| 290 |
except Exception as e:
|
| 291 |
+
show_error(self.global_settings, "Error handling database state change", str(e))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 292 |
|
| 293 |
def _check_and_update_home_tab(self, index):
|
| 294 |
if self.global_settings.main_window.view.tab_widget.tabText(index) == "Home":
|
|
|
|
| 309 |
"""Handle changes to the annotation file selection"""
|
| 310 |
self.global_settings.set_current_annotation_file(new_file)
|
| 311 |
|
| 312 |
+
def _update_cspr_related_ui(self):
|
| 313 |
+
# Implementation to update UI elements that depend on CSPR files
|
| 314 |
+
pass
|
| 315 |
+
|
| 316 |
+
def _update_gbff_related_ui(self):
|
| 317 |
+
# Implementation to update UI elements that depend on GBFF files
|
| 318 |
+
pass
|
| 319 |
+
|
| 320 |
+
def _update_validation_state(self, is_valid):
|
| 321 |
+
# Implementation to update UI elements based on validation state
|
| 322 |
+
pass
|
| 323 |
+
|
| 324 |
+
def handle_search_type_change(self):
|
| 325 |
+
"""Update UI elements based on search type"""
|
| 326 |
+
try:
|
| 327 |
+
search_type = self.view.get_search_type()
|
| 328 |
+
|
| 329 |
+
# Update button text
|
| 330 |
+
if search_type in ['position', 'sequence']:
|
| 331 |
+
self.view.push_button_find_view_targets.setText("View Targets")
|
| 332 |
+
else: # 'feature'
|
| 333 |
+
self.view.push_button_find_view_targets.setText("Find Targets")
|
| 334 |
+
|
| 335 |
+
except Exception as e:
|
| 336 |
+
self.logger.error(f"Error updating search type UI: {str(e)}")
|
| 337 |
+
|
|
@@ -11,25 +11,26 @@ from utils.LoggingMixin import LoggingMixin
|
|
| 11 |
class MainWindowController(LoggingMixin):
|
| 12 |
def __init__(self, global_settings):
|
| 13 |
LoggingMixin.__init__(self)
|
| 14 |
-
self.
|
| 15 |
self.tab_widgets = {
|
| 16 |
'widgets': {},
|
| 17 |
'controllers': {}
|
| 18 |
}
|
| 19 |
self.startup_controller = None
|
| 20 |
-
self.is_first_time_startup = self.
|
| 21 |
self.shared_tab_size = QSize(850, 850)
|
| 22 |
self.startup_size = QSize(750, 550)
|
| 23 |
self.current_tab = None
|
|
|
|
| 24 |
|
| 25 |
try:
|
| 26 |
-
self.view = MainWindowView(
|
| 27 |
self._setup_connections()
|
| 28 |
self._init_ui()
|
| 29 |
-
self.
|
| 30 |
except Exception as e:
|
| 31 |
self.log_error("__init__", e)
|
| 32 |
-
show_error(self.
|
| 33 |
|
| 34 |
def _setup_connections(self):
|
| 35 |
self.log_method_call("_setup_connections")
|
|
@@ -44,13 +45,20 @@ class MainWindowController(LoggingMixin):
|
|
| 44 |
self.view.close_window_button.clicked.connect(self._close_window)
|
| 45 |
self.view.minimize_window_button.clicked.connect(self._minimize_window)
|
| 46 |
self.view.maximize_window_button.clicked.connect(self._maximize_window)
|
| 47 |
-
self.view.theme_toggle_button.clicked.connect(self._toggle_theme)
|
| 48 |
|
| 49 |
# Tab bar
|
| 50 |
self.view.tab_widget.tab_closed.connect(self._on_tab_closed)
|
| 51 |
self.view.tab_widget.tabCloseRequested.connect(self._close_tab)
|
|
|
|
| 52 |
|
| 53 |
-
self.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
def _init_ui(self):
|
| 56 |
self.log_method_call("_init_ui")
|
|
@@ -60,8 +68,8 @@ class MainWindowController(LoggingMixin):
|
|
| 60 |
self._open_startup_tab()
|
| 61 |
return
|
| 62 |
|
| 63 |
-
db_path = self.
|
| 64 |
-
is_valid, message = self.
|
| 65 |
|
| 66 |
if db_path and is_valid:
|
| 67 |
self.log_info(f"Database path is valid: {db_path}")
|
|
@@ -77,11 +85,11 @@ class MainWindowController(LoggingMixin):
|
|
| 77 |
|
| 78 |
def _open_startup_tab(self):
|
| 79 |
try:
|
| 80 |
-
self.startup_controller = self.
|
| 81 |
self.open_new_tab("Startup", self.startup_controller)
|
| 82 |
except Exception as e:
|
| 83 |
self.log_error("_open_startup_tab", e)
|
| 84 |
-
show_error(self.
|
| 85 |
|
| 86 |
def _switch_to_home_from_startup(self):
|
| 87 |
self.log_method_call("_switch_to_home_from_startup")
|
|
@@ -109,20 +117,20 @@ class MainWindowController(LoggingMixin):
|
|
| 109 |
self.log_debug(f"Window centered at {self.view.pos()}")
|
| 110 |
except Exception as e:
|
| 111 |
self.log_error("_center_window", e)
|
| 112 |
-
show_error(self.
|
| 113 |
|
| 114 |
def _change_database_directory(self):
|
| 115 |
try:
|
| 116 |
new_directory = QtWidgets.QFileDialog.getExistingDirectory(
|
| 117 |
self.view, "Select Database Directory",
|
| 118 |
-
self.
|
| 119 |
QtWidgets.QFileDialog.Option.ShowDirsOnly
|
| 120 |
)
|
| 121 |
|
| 122 |
if not new_directory:
|
| 123 |
return
|
| 124 |
|
| 125 |
-
is_valid, message = self.
|
| 126 |
if is_valid:
|
| 127 |
self._process_valid_directory(new_directory)
|
| 128 |
else:
|
|
@@ -130,7 +138,7 @@ class MainWindowController(LoggingMixin):
|
|
| 130 |
|
| 131 |
except Exception as e:
|
| 132 |
self.log_error("_change_database_directory", e)
|
| 133 |
-
show_error(self.
|
| 134 |
|
| 135 |
def _handle_invalid_directory(self, new_directory, message):
|
| 136 |
reply = QtWidgets.QMessageBox.question(
|
|
@@ -143,16 +151,16 @@ class MainWindowController(LoggingMixin):
|
|
| 143 |
)
|
| 144 |
|
| 145 |
if reply == QtWidgets.QMessageBox.StandardButton.Yes:
|
| 146 |
-
self.
|
| 147 |
-
self.
|
| 148 |
self.open_new_genome_tab()
|
| 149 |
else:
|
| 150 |
show_message("Operation Cancelled", "Database directory change cancelled.")
|
| 151 |
|
| 152 |
def _process_valid_directory(self, new_directory):
|
| 153 |
try:
|
| 154 |
-
self.
|
| 155 |
-
self.
|
| 156 |
show_message("Success", "Database directory changed successfully.")
|
| 157 |
|
| 158 |
if (self.startup_controller and
|
|
@@ -160,7 +168,7 @@ class MainWindowController(LoggingMixin):
|
|
| 160 |
self._switch_to_home_from_startup()
|
| 161 |
except Exception as e:
|
| 162 |
self.log_error("_process_valid_directory", e)
|
| 163 |
-
show_error(self.
|
| 164 |
|
| 165 |
def _open_ncbi_website(self):
|
| 166 |
ncbi_page()
|
|
@@ -202,12 +210,12 @@ class MainWindowController(LoggingMixin):
|
|
| 202 |
def _open_home_tab(self):
|
| 203 |
"""Opens the home tab"""
|
| 204 |
try:
|
| 205 |
-
home_controller = self.
|
| 206 |
self.open_new_tab("Home", home_controller)
|
| 207 |
self.log_info("Home tab opened successfully")
|
| 208 |
except Exception as e:
|
| 209 |
self.log_error("_open_home_tab", e)
|
| 210 |
-
show_error(self.
|
| 211 |
|
| 212 |
def open_new_tab(self, title, content):
|
| 213 |
"""Opens a new tab with the given title and content"""
|
|
@@ -246,40 +254,69 @@ class MainWindowController(LoggingMixin):
|
|
| 246 |
|
| 247 |
except Exception as e:
|
| 248 |
self.log_error("open_new_tab", e)
|
| 249 |
-
show_error(self.
|
| 250 |
|
| 251 |
def _resize_for_tab(self, title):
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
|
| 262 |
-
#
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
self.current_tab = title
|
| 271 |
|
| 272 |
def _close_tab(self, index):
|
| 273 |
-
"""
|
| 274 |
-
Handle tab closure using CloseableTabWidget
|
| 275 |
-
"""
|
| 276 |
if 0 <= index < self.view.tab_widget.count():
|
| 277 |
title = self.view.tab_widget.tabText(index)
|
| 278 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 279 |
# Let CloseableTabWidget handle the widget cleanup
|
| 280 |
self.view.tab_widget.closeTab(index)
|
| 281 |
|
| 282 |
-
# Clean up
|
| 283 |
if title in self.tab_widgets['widgets']:
|
| 284 |
del self.tab_widgets['widgets'][title]
|
| 285 |
if title in self.tab_widgets['controllers']:
|
|
@@ -302,15 +339,15 @@ class MainWindowController(LoggingMixin):
|
|
| 302 |
|
| 303 |
def _toggle_theme(self):
|
| 304 |
try:
|
| 305 |
-
self.
|
| 306 |
self.view.update_theme_icon()
|
| 307 |
self.view.apply_theme()
|
| 308 |
except Exception as e:
|
| 309 |
-
show_error(self.
|
| 310 |
|
| 311 |
def show(self):
|
| 312 |
try:
|
| 313 |
-
saved_position = self.
|
| 314 |
if saved_position:
|
| 315 |
self.view.move(saved_position)
|
| 316 |
else:
|
|
@@ -319,8 +356,8 @@ class MainWindowController(LoggingMixin):
|
|
| 319 |
self.view.show()
|
| 320 |
self.view.apply_theme()
|
| 321 |
except Exception as e:
|
| 322 |
-
self.
|
| 323 |
-
show_error(self.
|
| 324 |
|
| 325 |
def open_new_genome_tab(self):
|
| 326 |
# Check if the New Genome tab already exists
|
|
@@ -377,8 +414,33 @@ class MainWindowController(LoggingMixin):
|
|
| 377 |
self._resize_for_tab("Home")
|
| 378 |
|
| 379 |
except Exception as e:
|
| 380 |
-
self.
|
| 381 |
-
show_error(self.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 382 |
|
| 383 |
|
| 384 |
|
|
|
|
| 11 |
class MainWindowController(LoggingMixin):
|
| 12 |
def __init__(self, global_settings):
|
| 13 |
LoggingMixin.__init__(self)
|
| 14 |
+
self.settings = global_settings
|
| 15 |
self.tab_widgets = {
|
| 16 |
'widgets': {},
|
| 17 |
'controllers': {}
|
| 18 |
}
|
| 19 |
self.startup_controller = None
|
| 20 |
+
self.is_first_time_startup = self.settings.is_first_time_startup
|
| 21 |
self.shared_tab_size = QSize(850, 850)
|
| 22 |
self.startup_size = QSize(750, 550)
|
| 23 |
self.current_tab = None
|
| 24 |
+
self.previous_size = None
|
| 25 |
|
| 26 |
try:
|
| 27 |
+
self.view = MainWindowView(self.settings)
|
| 28 |
self._setup_connections()
|
| 29 |
self._init_ui()
|
| 30 |
+
self.settings.check_and_emit_first_time_startup()
|
| 31 |
except Exception as e:
|
| 32 |
self.log_error("__init__", e)
|
| 33 |
+
show_error(self.settings, "Error initializing MainWindowController", str(e))
|
| 34 |
|
| 35 |
def _setup_connections(self):
|
| 36 |
self.log_method_call("_setup_connections")
|
|
|
|
| 45 |
self.view.close_window_button.clicked.connect(self._close_window)
|
| 46 |
self.view.minimize_window_button.clicked.connect(self._minimize_window)
|
| 47 |
self.view.maximize_window_button.clicked.connect(self._maximize_window)
|
|
|
|
| 48 |
|
| 49 |
# Tab bar
|
| 50 |
self.view.tab_widget.tab_closed.connect(self._on_tab_closed)
|
| 51 |
self.view.tab_widget.tabCloseRequested.connect(self._close_tab)
|
| 52 |
+
self.view.tab_widget.currentChanged.connect(self._on_current_tab_changed)
|
| 53 |
|
| 54 |
+
self.settings.first_time_startup.connect(self._handle_first_time_startup)
|
| 55 |
+
|
| 56 |
+
# Add Button Menu
|
| 57 |
+
self.view.action_new_genome.triggered.connect(self.open_new_genome_tab)
|
| 58 |
+
self.view.action_new_endonuclease.triggered.connect(self.open_new_endonuclease_tab)
|
| 59 |
+
|
| 60 |
+
# Settings Menu
|
| 61 |
+
self.view.action_toggle_theme.triggered.connect(self._toggle_theme)
|
| 62 |
|
| 63 |
def _init_ui(self):
|
| 64 |
self.log_method_call("_init_ui")
|
|
|
|
| 68 |
self._open_startup_tab()
|
| 69 |
return
|
| 70 |
|
| 71 |
+
db_path = self.settings.get_db_path()
|
| 72 |
+
is_valid, message = self.settings.validate_db_path(db_path)
|
| 73 |
|
| 74 |
if db_path and is_valid:
|
| 75 |
self.log_info(f"Database path is valid: {db_path}")
|
|
|
|
| 85 |
|
| 86 |
def _open_startup_tab(self):
|
| 87 |
try:
|
| 88 |
+
self.startup_controller = self.settings.get_startup_window()
|
| 89 |
self.open_new_tab("Startup", self.startup_controller)
|
| 90 |
except Exception as e:
|
| 91 |
self.log_error("_open_startup_tab", e)
|
| 92 |
+
show_error(self.settings, "Error opening startup tab", str(e))
|
| 93 |
|
| 94 |
def _switch_to_home_from_startup(self):
|
| 95 |
self.log_method_call("_switch_to_home_from_startup")
|
|
|
|
| 117 |
self.log_debug(f"Window centered at {self.view.pos()}")
|
| 118 |
except Exception as e:
|
| 119 |
self.log_error("_center_window", e)
|
| 120 |
+
show_error(self.settings, "Error centering window", str(e))
|
| 121 |
|
| 122 |
def _change_database_directory(self):
|
| 123 |
try:
|
| 124 |
new_directory = QtWidgets.QFileDialog.getExistingDirectory(
|
| 125 |
self.view, "Select Database Directory",
|
| 126 |
+
self.settings.get_db_path(),
|
| 127 |
QtWidgets.QFileDialog.Option.ShowDirsOnly
|
| 128 |
)
|
| 129 |
|
| 130 |
if not new_directory:
|
| 131 |
return
|
| 132 |
|
| 133 |
+
is_valid, message = self.settings.validate_db_path(new_directory)
|
| 134 |
if is_valid:
|
| 135 |
self._process_valid_directory(new_directory)
|
| 136 |
else:
|
|
|
|
| 138 |
|
| 139 |
except Exception as e:
|
| 140 |
self.log_error("_change_database_directory", e)
|
| 141 |
+
show_error(self.settings, "Error changing database directory", str(e))
|
| 142 |
|
| 143 |
def _handle_invalid_directory(self, new_directory, message):
|
| 144 |
reply = QtWidgets.QMessageBox.question(
|
|
|
|
| 151 |
)
|
| 152 |
|
| 153 |
if reply == QtWidgets.QMessageBox.StandardButton.Yes:
|
| 154 |
+
self.settings.save_db_path(new_directory)
|
| 155 |
+
self.settings.update_db_state()
|
| 156 |
self.open_new_genome_tab()
|
| 157 |
else:
|
| 158 |
show_message("Operation Cancelled", "Database directory change cancelled.")
|
| 159 |
|
| 160 |
def _process_valid_directory(self, new_directory):
|
| 161 |
try:
|
| 162 |
+
self.settings.save_db_path(new_directory)
|
| 163 |
+
self.settings.update_db_state()
|
| 164 |
show_message("Success", "Database directory changed successfully.")
|
| 165 |
|
| 166 |
if (self.startup_controller and
|
|
|
|
| 168 |
self._switch_to_home_from_startup()
|
| 169 |
except Exception as e:
|
| 170 |
self.log_error("_process_valid_directory", e)
|
| 171 |
+
show_error(self.settings, "Error processing directory", str(e))
|
| 172 |
|
| 173 |
def _open_ncbi_website(self):
|
| 174 |
ncbi_page()
|
|
|
|
| 210 |
def _open_home_tab(self):
|
| 211 |
"""Opens the home tab"""
|
| 212 |
try:
|
| 213 |
+
home_controller = self.settings.get_home_window()
|
| 214 |
self.open_new_tab("Home", home_controller)
|
| 215 |
self.log_info("Home tab opened successfully")
|
| 216 |
except Exception as e:
|
| 217 |
self.log_error("_open_home_tab", e)
|
| 218 |
+
show_error(self.settings, "Error opening home tab", str(e))
|
| 219 |
|
| 220 |
def open_new_tab(self, title, content):
|
| 221 |
"""Opens a new tab with the given title and content"""
|
|
|
|
| 254 |
|
| 255 |
except Exception as e:
|
| 256 |
self.log_error("open_new_tab", e)
|
| 257 |
+
show_error(self.settings, f"Error opening tab '{title}'", str(e))
|
| 258 |
|
| 259 |
def _resize_for_tab(self, title):
|
| 260 |
+
try:
|
| 261 |
+
if title == "Startup":
|
| 262 |
+
# For Startup tab, set fixed size and disable maximize button
|
| 263 |
+
self.view.setFixedSize(self.startup_size)
|
| 264 |
+
self.view.setWindowFlags(self.view.windowFlags() & ~Qt.WindowType.WindowMaximizeButtonHint)
|
| 265 |
+
elif title in ["View Targets", "Multitargeting Analysis"]:
|
| 266 |
+
# Store current size before applying constraints
|
| 267 |
+
if self.current_tab not in ["View Targets", "Multitargeting Analysis"]:
|
| 268 |
+
self.previous_size = self.view.size()
|
| 269 |
+
|
| 270 |
+
# Set minimum dimensions for these tabs
|
| 271 |
+
min_width = 1300
|
| 272 |
+
min_height = 800
|
| 273 |
+
|
| 274 |
+
# Calculate new dimensions
|
| 275 |
+
new_width = max(self.view.width(), min_width)
|
| 276 |
+
new_height = max(self.view.height(), min_height)
|
| 277 |
+
|
| 278 |
+
# Only resize if dimensions need to increase
|
| 279 |
+
if new_width > self.view.width() or new_height > self.view.height():
|
| 280 |
+
self.view.resize(QSize(new_width, new_height))
|
| 281 |
+
|
| 282 |
+
# Set minimum size constraints
|
| 283 |
+
self.view.setMinimumSize(QSize(min_width, min_height))
|
| 284 |
+
self.view.setMaximumSize(QtCore.QSize(16777215, 16777215))
|
| 285 |
+
self.view.setWindowFlags(self.view.windowFlags() | Qt.WindowType.WindowMaximizeButtonHint)
|
| 286 |
+
else:
|
| 287 |
+
# For all other tabs
|
| 288 |
+
self.view.setMinimumSize(QSize(400, 300))
|
| 289 |
+
self.view.setMaximumSize(QtCore.QSize(16777215, 16777215))
|
| 290 |
+
self.view.setWindowFlags(self.view.windowFlags() | Qt.WindowType.WindowMaximizeButtonHint)
|
| 291 |
+
|
| 292 |
+
# Restore previous size if available and coming from View Targets or Multi-targeting Analysis
|
| 293 |
+
if self.current_tab in ["View Targets", "Multitargeting Analysis"] and self.previous_size:
|
| 294 |
+
self.view.resize(self.previous_size)
|
| 295 |
+
elif self.current_tab == "Startup" or self.view.size() == self.startup_size:
|
| 296 |
+
self.view.resize(self.shared_tab_size)
|
| 297 |
|
| 298 |
+
# Ensure window flags are updated
|
| 299 |
+
self.view.show()
|
| 300 |
+
|
| 301 |
+
# Update the current tab
|
| 302 |
+
self.current_tab = title
|
| 303 |
+
|
| 304 |
+
except Exception as e:
|
| 305 |
+
self.log_error("_resize_for_tab", e)
|
|
|
|
| 306 |
|
| 307 |
def _close_tab(self, index):
|
| 308 |
+
"""Handle tab closure using CloseableTabWidget"""
|
|
|
|
|
|
|
| 309 |
if 0 <= index < self.view.tab_widget.count():
|
| 310 |
title = self.view.tab_widget.tabText(index)
|
| 311 |
|
| 312 |
+
# Store size before closing View Targets tab
|
| 313 |
+
if title == "View Targets":
|
| 314 |
+
self.previous_size = self.view.size()
|
| 315 |
+
|
| 316 |
# Let CloseableTabWidget handle the widget cleanup
|
| 317 |
self.view.tab_widget.closeTab(index)
|
| 318 |
|
| 319 |
+
# Clean up references
|
| 320 |
if title in self.tab_widgets['widgets']:
|
| 321 |
del self.tab_widgets['widgets'][title]
|
| 322 |
if title in self.tab_widgets['controllers']:
|
|
|
|
| 339 |
|
| 340 |
def _toggle_theme(self):
|
| 341 |
try:
|
| 342 |
+
self.settings.set_theme("dark" if self.settings.get_theme() == "light" else "light")
|
| 343 |
self.view.update_theme_icon()
|
| 344 |
self.view.apply_theme()
|
| 345 |
except Exception as e:
|
| 346 |
+
show_error(self.settings, "Error toggling theme", str(e))
|
| 347 |
|
| 348 |
def show(self):
|
| 349 |
try:
|
| 350 |
+
saved_position = self.settings.load_window_position("main_window")
|
| 351 |
if saved_position:
|
| 352 |
self.view.move(saved_position)
|
| 353 |
else:
|
|
|
|
| 356 |
self.view.show()
|
| 357 |
self.view.apply_theme()
|
| 358 |
except Exception as e:
|
| 359 |
+
self.log_error("show", e)
|
| 360 |
+
show_error(self.settings, "Error showing main window", e)
|
| 361 |
|
| 362 |
def open_new_genome_tab(self):
|
| 363 |
# Check if the New Genome tab already exists
|
|
|
|
| 414 |
self._resize_for_tab("Home")
|
| 415 |
|
| 416 |
except Exception as e:
|
| 417 |
+
self.log_error("close_new_genome_and_switch_to_home", e)
|
| 418 |
+
show_error(self.settings, "Error switching to Home tab", str(e))
|
| 419 |
+
|
| 420 |
+
def _on_current_tab_changed(self, index):
|
| 421 |
+
"""Handle tab change events"""
|
| 422 |
+
try:
|
| 423 |
+
if index >= 0:
|
| 424 |
+
new_tab_title = self.view.tab_widget.tabText(index)
|
| 425 |
+
old_tab_title = self.current_tab
|
| 426 |
+
|
| 427 |
+
# Store current size if coming from a non-Startup tab
|
| 428 |
+
if old_tab_title and old_tab_title != "Startup":
|
| 429 |
+
self.previous_size = self.view.size()
|
| 430 |
+
|
| 431 |
+
self._resize_for_tab(new_tab_title)
|
| 432 |
+
|
| 433 |
+
except Exception as e:
|
| 434 |
+
self.log_error("_on_current_tab_changed", e)
|
| 435 |
+
|
| 436 |
+
def open_new_endonuclease_tab(self):
|
| 437 |
+
"""Opens the new endonuclease window"""
|
| 438 |
+
try:
|
| 439 |
+
new_endonuclease_controller = self.settings.get_new_endonuclease_window()
|
| 440 |
+
new_endonuclease_controller.view.show() # Show as window instead of tab
|
| 441 |
+
except Exception as e:
|
| 442 |
+
self.log_error("open_new_endonuclease_tab", e)
|
| 443 |
+
show_error(self.settings, "Error opening new endonuclease window", str(e))
|
| 444 |
|
| 445 |
|
| 446 |
|
|
@@ -1,7 +1,7 @@
|
|
| 1 |
from PyQt6.QtWidgets import QMainWindow
|
| 2 |
from views.MultitargetingWindowView import MultitargetingWindowView
|
| 3 |
from models.MultitargetingWindowModel import MultitargetingWindowModel
|
| 4 |
-
from utils.ui import show_error
|
| 5 |
|
| 6 |
class MultitargetingWindowController(QMainWindow):
|
| 7 |
def __init__(self, global_settings):
|
|
@@ -35,6 +35,9 @@ class MultitargetingWindowController(QMainWindow):
|
|
| 35 |
# Initialize plots
|
| 36 |
self._view.setup_plots()
|
| 37 |
|
|
|
|
|
|
|
|
|
|
| 38 |
except Exception as e:
|
| 39 |
self.logger.error(f"Error in _init_ui: {str(e)}")
|
| 40 |
show_error(self.settings, "Error", f"Failed to initialize UI: {str(e)}")
|
|
@@ -47,13 +50,15 @@ class MultitargetingWindowController(QMainWindow):
|
|
| 47 |
|
| 48 |
# Buttons
|
| 49 |
self._view.push_button_analyze.clicked.connect(self._on_analyze_clicked)
|
| 50 |
-
self._view.push_button_statistics_overview.clicked.connect(self._on_statistics_overview_clicked)
|
| 51 |
-
self._view.tool_button_sql_settings.clicked.connect(self._on_sql_settings_clicked)
|
| 52 |
|
| 53 |
# Table selection
|
| 54 |
self._view.table_seeds.itemSelectionChanged.connect(self._on_seed_selected)
|
| 55 |
self._view.check_box_select_all.stateChanged.connect(self._on_select_all_changed)
|
| 56 |
|
|
|
|
|
|
|
| 57 |
def _on_organism_changed(self, index):
|
| 58 |
"""Handle organism selection change"""
|
| 59 |
try:
|
|
@@ -215,7 +220,7 @@ class MultitargetingWindowController(QMainWindow):
|
|
| 215 |
|
| 216 |
def _update_analysis_button_state(self):
|
| 217 |
"""Update analyze button enabled state"""
|
| 218 |
-
has_organism = bool(self._view.
|
| 219 |
has_endo = bool(self._view.combo_box_endonuclease.currentText())
|
| 220 |
self._view.push_button_analyze.setEnabled(has_organism and has_endo)
|
| 221 |
|
|
@@ -225,14 +230,22 @@ class MultitargetingWindowController(QMainWindow):
|
|
| 225 |
self._view.update_plots(None, None, None)
|
| 226 |
|
| 227 |
def _update_plots(self):
|
| 228 |
-
"""Update all plots with current data"""
|
| 229 |
try:
|
| 230 |
-
# Get repeats vs seeds data first
|
| 231 |
repeats_data = self._model.get_repeats_vs_seeds_data()
|
| 232 |
-
|
| 233 |
-
# Get sequences vs repeats data
|
| 234 |
sequences_data = self._model.get_seeds_vs_repeats_data()
|
| 235 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 236 |
# Update all plots at once
|
| 237 |
self._view.update_plots(repeats_data, sequences_data, None) # chromosome_data will be updated on seed selection
|
| 238 |
|
|
@@ -254,3 +267,61 @@ class MultitargetingWindowController(QMainWindow):
|
|
| 254 |
"""Get settings from SQL settings dialog"""
|
| 255 |
# Implement getting settings from dialog
|
| 256 |
return {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from PyQt6.QtWidgets import QMainWindow
|
| 2 |
from views.MultitargetingWindowView import MultitargetingWindowView
|
| 3 |
from models.MultitargetingWindowModel import MultitargetingWindowModel
|
| 4 |
+
from utils.ui import show_error, show_message
|
| 5 |
|
| 6 |
class MultitargetingWindowController(QMainWindow):
|
| 7 |
def __init__(self, global_settings):
|
|
|
|
| 35 |
# Initialize plots
|
| 36 |
self._view.setup_plots()
|
| 37 |
|
| 38 |
+
# Connect max results line edit
|
| 39 |
+
self._view.line_edit_max_results.textChanged.connect(self._on_max_results_changed)
|
| 40 |
+
|
| 41 |
except Exception as e:
|
| 42 |
self.logger.error(f"Error in _init_ui: {str(e)}")
|
| 43 |
show_error(self.settings, "Error", f"Failed to initialize UI: {str(e)}")
|
|
|
|
| 50 |
|
| 51 |
# Buttons
|
| 52 |
self._view.push_button_analyze.clicked.connect(self._on_analyze_clicked)
|
| 53 |
+
# self._view.push_button_statistics_overview.clicked.connect(self._on_statistics_overview_clicked)
|
| 54 |
+
# self._view.tool_button_sql_settings.clicked.connect(self._on_sql_settings_clicked)
|
| 55 |
|
| 56 |
# Table selection
|
| 57 |
self._view.table_seeds.itemSelectionChanged.connect(self._on_seed_selected)
|
| 58 |
self._view.check_box_select_all.stateChanged.connect(self._on_select_all_changed)
|
| 59 |
|
| 60 |
+
self._view.push_button_export_selected_gRNAs.clicked.connect(self._handle_export)
|
| 61 |
+
|
| 62 |
def _on_organism_changed(self, index):
|
| 63 |
"""Handle organism selection change"""
|
| 64 |
try:
|
|
|
|
| 220 |
|
| 221 |
def _update_analysis_button_state(self):
|
| 222 |
"""Update analyze button enabled state"""
|
| 223 |
+
has_organism = bool(self._view.combo_box_organism.currentText())
|
| 224 |
has_endo = bool(self._view.combo_box_endonuclease.currentText())
|
| 225 |
self._view.push_button_analyze.setEnabled(has_organism and has_endo)
|
| 226 |
|
|
|
|
| 230 |
self._view.update_plots(None, None, None)
|
| 231 |
|
| 232 |
def _update_plots(self):
|
|
|
|
| 233 |
try:
|
|
|
|
| 234 |
repeats_data = self._model.get_repeats_vs_seeds_data()
|
|
|
|
|
|
|
| 235 |
sequences_data = self._model.get_seeds_vs_repeats_data()
|
| 236 |
|
| 237 |
+
# Get statistics for overview tab
|
| 238 |
+
stats = self._model.calculate_statistics()
|
| 239 |
+
|
| 240 |
+
# Update statistics labels
|
| 241 |
+
if stats:
|
| 242 |
+
self._view.update_statistics_labels(
|
| 243 |
+
total_repeats=stats.get('repeat_count', 0),
|
| 244 |
+
avg_repeats=stats.get('average', 0),
|
| 245 |
+
median_repeats=stats.get('median', 0),
|
| 246 |
+
mode_repeats=stats.get('mode', 0)
|
| 247 |
+
)
|
| 248 |
+
|
| 249 |
# Update all plots at once
|
| 250 |
self._view.update_plots(repeats_data, sequences_data, None) # chromosome_data will be updated on seed selection
|
| 251 |
|
|
|
|
| 267 |
"""Get settings from SQL settings dialog"""
|
| 268 |
# Implement getting settings from dialog
|
| 269 |
return {}
|
| 270 |
+
|
| 271 |
+
def _on_max_results_changed(self, value):
|
| 272 |
+
"""Handle changes to max results setting"""
|
| 273 |
+
try:
|
| 274 |
+
if value == "": # Handle empty input
|
| 275 |
+
self._model.set_row_limit(1000) # Reset to default
|
| 276 |
+
return
|
| 277 |
+
|
| 278 |
+
# Convert to int and update model
|
| 279 |
+
limit = int(value)
|
| 280 |
+
if limit <= 0: # Handle negative or zero values
|
| 281 |
+
limit = -1 # Use -1 to indicate no limit
|
| 282 |
+
self._model.set_row_limit(limit)
|
| 283 |
+
|
| 284 |
+
except ValueError:
|
| 285 |
+
# Reset to default if invalid input
|
| 286 |
+
self._model.set_row_limit(1000)
|
| 287 |
+
self._view.line_edit_max_results.setText("1000")
|
| 288 |
+
|
| 289 |
+
def _handle_export(self):
|
| 290 |
+
"""Handle export button click"""
|
| 291 |
+
try:
|
| 292 |
+
selected_items = []
|
| 293 |
+
selected_rows = self._view.table_seeds.selectedItems()
|
| 294 |
+
|
| 295 |
+
if not selected_rows:
|
| 296 |
+
show_message(
|
| 297 |
+
"Warning",
|
| 298 |
+
"Please select at least one row to export."
|
| 299 |
+
)
|
| 300 |
+
return
|
| 301 |
+
|
| 302 |
+
# Get unique rows (since selecting one row selects all its columns)
|
| 303 |
+
selected_row_numbers = set()
|
| 304 |
+
for item in selected_rows:
|
| 305 |
+
selected_row_numbers.add(item.row())
|
| 306 |
+
|
| 307 |
+
# For each selected row, create a dictionary with the row data
|
| 308 |
+
for row in selected_row_numbers:
|
| 309 |
+
item_data = {
|
| 310 |
+
'seed': self._view.table_seeds.item(row, 0).text(),
|
| 311 |
+
'total_repeats': self._view.table_seeds.item(row, 1).text(),
|
| 312 |
+
'avg_repeats_or_scaffold': self._view.table_seeds.item(row, 2).text(),
|
| 313 |
+
'consensus_sequence': self._view.table_seeds.item(row, 3).text(),
|
| 314 |
+
'percent_consensus': self._view.table_seeds.item(row, 4).text(),
|
| 315 |
+
'score': self._view.table_seeds.item(row, 5).text(),
|
| 316 |
+
'pam': self._view.table_seeds.item(row, 6).text(),
|
| 317 |
+
'strand': self._view.table_seeds.item(row, 7).text()
|
| 318 |
+
}
|
| 319 |
+
selected_items.append(item_data)
|
| 320 |
+
|
| 321 |
+
# Get export window controller and show dialog
|
| 322 |
+
export_controller = self.settings.get_export_selected_grnas_window()
|
| 323 |
+
export_controller.show_dialog(selected_items, "Multitargeting")
|
| 324 |
+
|
| 325 |
+
except Exception as e:
|
| 326 |
+
self.logger.error(f"Error handling export: {str(e)}")
|
| 327 |
+
show_error(self.settings, "Export Error", str(e))
|
|
@@ -1,5 +1,5 @@
|
|
| 1 |
from PyQt6 import QtWidgets, QtCore, QtGui
|
| 2 |
-
from utils.ui import show_error, show_message
|
| 3 |
from models.NCBIWindowModel import NCBIWindowModel, PandasModel, CustomProxyModel
|
| 4 |
from views.NCBIWindowView import NCBIWindowView
|
| 5 |
import os
|
|
|
|
| 1 |
from PyQt6 import QtWidgets, QtCore, QtGui
|
| 2 |
+
from utils.ui import show_error, show_message
|
| 3 |
from models.NCBIWindowModel import NCBIWindowModel, PandasModel, CustomProxyModel
|
| 4 |
from views.NCBIWindowView import NCBIWindowView
|
| 5 |
import os
|
|
@@ -12,6 +12,7 @@ class NewEndonucleaseController:
|
|
| 12 |
self.model = NewEndonucleaseModel(self.settings)
|
| 13 |
self.view = NewEndonucleaseView(self.settings)
|
| 14 |
self.model.endonuclease_updated.connect(self._on_endonuclease_updated)
|
|
|
|
| 15 |
|
| 16 |
self._setup_connections()
|
| 17 |
self._init_ui()
|
|
|
|
| 12 |
self.model = NewEndonucleaseModel(self.settings)
|
| 13 |
self.view = NewEndonucleaseView(self.settings)
|
| 14 |
self.model.endonuclease_updated.connect(self._on_endonuclease_updated)
|
| 15 |
+
self.settings.theme_changed.connect(self.view.apply_theme)
|
| 16 |
|
| 17 |
self._setup_connections()
|
| 18 |
self._init_ui()
|
|
@@ -1,277 +0,0 @@
|
|
| 1 |
-
import os, platform
|
| 2 |
-
from PyQt5 import QtWidgets, uic, QtCore, QtGui, Qt
|
| 3 |
-
from functools import partial
|
| 4 |
-
import models.GlobalSettings as GlobalSettings
|
| 5 |
-
from utils.ui import show_message, show_error, scale_ui, center_ui
|
| 6 |
-
|
| 7 |
-
logger = GlobalSettings.logger
|
| 8 |
-
|
| 9 |
-
class OffTarget(QtWidgets.QMainWindow):
|
| 10 |
-
def __init__(self):
|
| 11 |
-
try:
|
| 12 |
-
super(OffTarget, self).__init__()
|
| 13 |
-
uic.loadUi(GlobalSettings.appdir + 'ui/off_target.ui', self)
|
| 14 |
-
self.setWindowIcon(Qt.QIcon(GlobalSettings.appdir + "cas9image.ico"))
|
| 15 |
-
self.setWindowTitle("Off-Target Analysis")
|
| 16 |
-
self.progressBar.setMinimum(0)
|
| 17 |
-
self.progressBar.setMaximum(100)
|
| 18 |
-
self.progressBar.setValue(0)
|
| 19 |
-
self.run_clicked = False
|
| 20 |
-
self.Run.clicked.connect(self.run_analysis)
|
| 21 |
-
# self.tolerancehorizontalSlider.valueChanged.connect(self.tol_change)
|
| 22 |
-
# self.tolerancehorizontalSlider.setMaximum(100)
|
| 23 |
-
# self.tolerancehorizontalSlider.setMinimum(0)
|
| 24 |
-
self.tolerance = 0.0
|
| 25 |
-
|
| 26 |
-
self.cancelButton.clicked.connect(self.exit)
|
| 27 |
-
self.fill_data_dropdown()
|
| 28 |
-
self.perc = False
|
| 29 |
-
self.bool_temp = False
|
| 30 |
-
self.running = False
|
| 31 |
-
self.process = QtCore.QProcess()
|
| 32 |
-
self.output_path = ''
|
| 33 |
-
|
| 34 |
-
groupbox_style = """
|
| 35 |
-
QGroupBox:title{subcontrol-origin: margin;
|
| 36 |
-
left: 10px;
|
| 37 |
-
padding: 0 5px 0 5px;}
|
| 38 |
-
QGroupBox#Step1{border: 2px solid rgb(111,181,110);
|
| 39 |
-
border-radius: 9px;
|
| 40 |
-
font: bold 14pt 'Arial';
|
| 41 |
-
margin-top: 10px;}"""
|
| 42 |
-
|
| 43 |
-
self.Step1.setStyleSheet(groupbox_style)
|
| 44 |
-
self.Step2.setStyleSheet(groupbox_style.replace("Step1", "Step2"))
|
| 45 |
-
self.Step3.setStyleSheet(groupbox_style.replace("Step1", "Step3"))
|
| 46 |
-
|
| 47 |
-
scale_ui(self, custom_scale_width=400, custom_scale_height=450)
|
| 48 |
-
|
| 49 |
-
except Exception as e:
|
| 50 |
-
show_error("Error initializing OffTarget class.", e)
|
| 51 |
-
|
| 52 |
-
#copied from MT to fill in the chromo and endo dropdowns based on CSPR files user provided at the startup
|
| 53 |
-
def fill_data_dropdown(self):
|
| 54 |
-
try:
|
| 55 |
-
try:
|
| 56 |
-
self.EndocomboBox.diconnect()
|
| 57 |
-
except:
|
| 58 |
-
pass
|
| 59 |
-
try:
|
| 60 |
-
self.OrgcomboBox.diconnect()
|
| 61 |
-
except:
|
| 62 |
-
pass
|
| 63 |
-
|
| 64 |
-
self.OrgcomboBox.clear()
|
| 65 |
-
self.EndocomboBox.clear()
|
| 66 |
-
self.mismatchcomboBox.clear()
|
| 67 |
-
|
| 68 |
-
self.organisms_to_files = {}
|
| 69 |
-
self.organisms_to_endos = {}
|
| 70 |
-
|
| 71 |
-
#fill in chromosome and endo dropdowns
|
| 72 |
-
onlyfiles = [f for f in os.listdir(GlobalSettings.CSPR_DB) if os.path.isfile(os.path.join(GlobalSettings.CSPR_DB , f))]
|
| 73 |
-
self.orgsandendos = {}
|
| 74 |
-
self.shortName = {}
|
| 75 |
-
for file in onlyfiles:
|
| 76 |
-
if file.find('.cspr') != -1:
|
| 77 |
-
newname = file[0:-4]
|
| 78 |
-
endo = newname[newname.rfind("_") + 1:-1]
|
| 79 |
-
hold = open(file, 'r')
|
| 80 |
-
buf = (hold.readline())
|
| 81 |
-
hold.close()
|
| 82 |
-
buf = str(buf)
|
| 83 |
-
buf = buf.strip()
|
| 84 |
-
species = buf.replace("GENOME: ", "")
|
| 85 |
-
|
| 86 |
-
if species in self.organisms_to_files:
|
| 87 |
-
self.organisms_to_files[species][endo] = [file, file.replace(".cspr", "_repeats.db")]
|
| 88 |
-
else:
|
| 89 |
-
self.organisms_to_files[species] = {}
|
| 90 |
-
self.organisms_to_files[species][endo] = [file, file.replace(".cspr", "_repeats.db")]
|
| 91 |
-
|
| 92 |
-
if species in self.organisms_to_endos:
|
| 93 |
-
self.organisms_to_endos[species].append(endo)
|
| 94 |
-
else:
|
| 95 |
-
self.organisms_to_endos[species] = [endo]
|
| 96 |
-
if self.OrgcomboBox.findText(species) == -1:
|
| 97 |
-
self.OrgcomboBox.addItem(species)
|
| 98 |
-
|
| 99 |
-
# fill in endos dropdown based on current organism
|
| 100 |
-
endos = self.organisms_to_endos[str(self.OrgcomboBox.currentText())]
|
| 101 |
-
self.EndocomboBox.addItems(endos)
|
| 102 |
-
self.OrgcomboBox.currentIndexChanged.connect(self.update_endos)
|
| 103 |
-
self.EndocomboBox.currentIndexChanged.connect(self.change_endos)
|
| 104 |
-
|
| 105 |
-
# update file names for current org/endo combo
|
| 106 |
-
self.cspr_file = self.organisms_to_files[str(self.OrgcomboBox.currentText())][endos[0]][0]
|
| 107 |
-
self.db_file = self.organisms_to_files[str(self.OrgcomboBox.currentText())][endos[0]][1]
|
| 108 |
-
|
| 109 |
-
#fill in Max Mismatch dropdown
|
| 110 |
-
mismatch_list = ['1','2','3','4','5','6','7','8','9','10']
|
| 111 |
-
self.mismatchcomboBox.addItems(mismatch_list)
|
| 112 |
-
self.mismatchcomboBox.setCurrentIndex(3) ### Max number of mismatches is 4 by default
|
| 113 |
-
except Exception as e:
|
| 114 |
-
show_error("Error in fill_data_dropdown() in OffTarget.", e)
|
| 115 |
-
|
| 116 |
-
def change_endos(self):
|
| 117 |
-
try:
|
| 118 |
-
#update file names based on current org/endo combo
|
| 119 |
-
self.cspr_file = self.organisms_to_files[str(self.OrgcomboBox.currentText())][str(self.EndocomboBox.currentText())][0]
|
| 120 |
-
self.db_file = self.organisms_to_files[str(self.OrgcomboBox.currentText())][str(self.EndocomboBox.currentText())][1]
|
| 121 |
-
except Exception as e:
|
| 122 |
-
show_error("Error in change_endos() in OffTarget.", e)
|
| 123 |
-
|
| 124 |
-
def update_endos(self):
|
| 125 |
-
try:
|
| 126 |
-
#try to disconnect index changed signal on endo dropdown if there is one
|
| 127 |
-
try:
|
| 128 |
-
self.EndocomboBox.currentIndexChanged.disconnect()
|
| 129 |
-
except:
|
| 130 |
-
pass
|
| 131 |
-
|
| 132 |
-
#clear endo dropdown and fill in with endos relative to the current organism
|
| 133 |
-
self.EndocomboBox.clear()
|
| 134 |
-
endos = self.organisms_to_endos[str(self.OrgcomboBox.currentText())]
|
| 135 |
-
self.EndocomboBox.addItems(endos)
|
| 136 |
-
self.cspr_file = self.organisms_to_files[str(self.OrgcomboBox.currentText())][endos[0]][0]
|
| 137 |
-
self.db_file = self.organisms_to_files[str(self.OrgcomboBox.currentText())][endos[0]][1]
|
| 138 |
-
|
| 139 |
-
#reconnect index changed signal on endo dropdown
|
| 140 |
-
self.EndocomboBox.currentIndexChanged.connect(self.change_endos)
|
| 141 |
-
except Exception as e:
|
| 142 |
-
show_error("Error in update_endos() in OffTarget.", e)
|
| 143 |
-
|
| 144 |
-
#tolerance slider / entry box. Allows for slider to update, or the user to input in text box
|
| 145 |
-
# def tol_change(self):
|
| 146 |
-
# try:
|
| 147 |
-
# if(self.tolerance == float(self.tolerancelineEdit.text())):
|
| 148 |
-
# self.tolerance = self.tolerancehorizontalSlider.value() / 100 * 0.5
|
| 149 |
-
# self.tolerance = round(self.tolerance, 3)
|
| 150 |
-
# self.tolerancelineEdit.setText(str(self.tolerance))
|
| 151 |
-
# else:
|
| 152 |
-
# self.tolerance = float(self.tolerancelineEdit.text())
|
| 153 |
-
# self.tolerance = round(self.tolerance, 3)
|
| 154 |
-
# self.tolerancehorizontalSlider.setValue(round(self.tolerance/0.5 * 100))
|
| 155 |
-
# except Exception as e:
|
| 156 |
-
# logger.critical("Error in tol_change() in OffTarget.")
|
| 157 |
-
# logger.critical(e)
|
| 158 |
-
# logger.critical(traceback.format_exc())
|
| 159 |
-
# exit(-1)
|
| 160 |
-
|
| 161 |
-
#run button linked to run_analysis, which is linked to the run button
|
| 162 |
-
def run_command(self):
|
| 163 |
-
try:
|
| 164 |
-
output_file_name = self.outputFileName.text().strip()
|
| 165 |
-
save_internally = self.outputCheckBox.isChecked()
|
| 166 |
-
if not output_file_name and not save_internally:
|
| 167 |
-
show_message(
|
| 168 |
-
fontSize=self.fontSize,
|
| 169 |
-
icon=QtWidgets.QMessageBox.Icon.Warning,
|
| 170 |
-
title="Input Error",
|
| 171 |
-
message="Please enter a valid output file name."
|
| 172 |
-
)
|
| 173 |
-
return
|
| 174 |
-
|
| 175 |
-
if save_internally:
|
| 176 |
-
full_output_path = os.path.join(GlobalSettings.appdir, 'local/local_output.txt')
|
| 177 |
-
else:
|
| 178 |
-
full_output_path = os.path.join(GlobalSettings.CSPR_DB, output_file_name)
|
| 179 |
-
if os.path.isfile(full_output_path):
|
| 180 |
-
show_message(
|
| 181 |
-
fontSize=self.fontSize,
|
| 182 |
-
icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 183 |
-
title="Error",
|
| 184 |
-
message="Output file already exists. Please choose a new output file name."
|
| 185 |
-
)
|
| 186 |
-
return
|
| 187 |
-
|
| 188 |
-
self.tolerance = self.toleranceSpinBox.value()
|
| 189 |
-
self.perc = False
|
| 190 |
-
self.bool_temp = False
|
| 191 |
-
self.running = False
|
| 192 |
-
|
| 193 |
-
app_path = GlobalSettings.appdir.replace('\\','/')
|
| 194 |
-
exe_path = os.path.join(app_path, 'OffTargetFolder', 'OT_Win.exe' if platform.system() == 'Windows' else 'OT_Lin' if platform.system() == 'Linux' else 'OT_Mac')
|
| 195 |
-
data_path = os.path.join(app_path, 'OffTargetFolder', 'temp.txt')
|
| 196 |
-
endo = self.EndocomboBox.currentText()
|
| 197 |
-
cspr_path = os.path.join(GlobalSettings.CSPR_DB, self.cspr_file)
|
| 198 |
-
db_path = os.path.join(GlobalSettings.CSPR_DB, self.db_file)
|
| 199 |
-
CASPER_info_path = os.path.join(app_path, 'CASPERinfo')
|
| 200 |
-
num_of_mismatches = int(self.mismatchcomboBox.currentText())
|
| 201 |
-
hsu = GlobalSettings.mainWindow.Results.endo_data[self.EndocomboBox.currentText()][2]
|
| 202 |
-
|
| 203 |
-
self.output_path = ' "' + full_output_path + '"'
|
| 204 |
-
|
| 205 |
-
cmd = f'"{exe_path}" "{data_path}" "{endo}" "{cspr_path}" "{db_path}" "{full_output_path}" "{CASPER_info_path}" {num_of_mismatches} {self.tolerance} {"TRUE" if self.AVG.isChecked() else "FALSE"} {"FALSE" if self.AVG.isChecked() else "TRUE"} "{hsu}"'
|
| 206 |
-
cmd = cmd.replace('/', '\\') if platform.system() == 'Windows' else cmd
|
| 207 |
-
|
| 208 |
-
def finished():
|
| 209 |
-
self.running = False
|
| 210 |
-
self.run_clicked = True
|
| 211 |
-
self.progressBar.setValue(100)
|
| 212 |
-
|
| 213 |
-
#used to know when data is ready to read from stdout
|
| 214 |
-
def dataReady():
|
| 215 |
-
#filter the data from stdout, bools used to know when the .exe starts outputting the progress
|
| 216 |
-
#percentages to be able to type cast them as floats and update the progress bar. Also, must
|
| 217 |
-
#split the input read based on '\n\ characters since the stdout read can read multiple lines at
|
| 218 |
-
#once and is all read in as raw bytes
|
| 219 |
-
raw_data = self.process.readAllStandardOutput().data().decode()
|
| 220 |
-
lines = raw_data.replace('\r', '').split('\n')
|
| 221 |
-
for line in lines:
|
| 222 |
-
if "Parsing Input Arguments" in line:
|
| 223 |
-
self.progressBar.setValue(10)
|
| 224 |
-
elif "Loading data for algorithm" in line:
|
| 225 |
-
self.progressBar.setValue(25)
|
| 226 |
-
elif "Running OffTarget Analysis" in line:
|
| 227 |
-
self.progressBar.setValue(50)
|
| 228 |
-
|
| 229 |
-
#connect QProcess to the dataReady func, and finished func, reset progressBar only if the outputfile name
|
| 230 |
-
#given does not already exist
|
| 231 |
-
self.process.readyReadStandardOutput.connect(partial(dataReady))
|
| 232 |
-
self.process.readyReadStandardError.connect(partial(dataReady))
|
| 233 |
-
self.progressBar.setValue(1)
|
| 234 |
-
QtCore.QTimer.singleShot(100, partial(self.process.start, cmd))
|
| 235 |
-
self.process.finished.connect(finished)
|
| 236 |
-
except Exception as e:
|
| 237 |
-
show_error("Error in run_command() in OffTarget.", e)
|
| 238 |
-
|
| 239 |
-
def run_analysis(self):
|
| 240 |
-
try:
|
| 241 |
-
#make sure an analysis isn't already running before starting
|
| 242 |
-
if(self.running == False):
|
| 243 |
-
self.running = True
|
| 244 |
-
self.run_command()
|
| 245 |
-
except Exception as e:
|
| 246 |
-
show_error("Error in run_analysis() in OffTarget.", e)
|
| 247 |
-
|
| 248 |
-
#exit linked to user clicking cancel, resets bools, and kills process if one was running
|
| 249 |
-
def exit(self):
|
| 250 |
-
try:
|
| 251 |
-
self.perc = False
|
| 252 |
-
self.bool_temp = False
|
| 253 |
-
self.running = False
|
| 254 |
-
self.process.kill()
|
| 255 |
-
self.hide()
|
| 256 |
-
|
| 257 |
-
local_output_path = os.path.join(GlobalSettings.appdir, 'local/local_output.txt')
|
| 258 |
-
|
| 259 |
-
if os.path.exists(local_output_path):
|
| 260 |
-
os.remove(local_output_path)
|
| 261 |
-
print("Local output file deleted successfully.")
|
| 262 |
-
else:
|
| 263 |
-
print("Local output file does not exist.")
|
| 264 |
-
except Exception as e:
|
| 265 |
-
show_error("Error in exit() in OffTarget.", e)
|
| 266 |
-
|
| 267 |
-
#closeEvent linked to user pressing the x in the top right of windows, resets bools, and
|
| 268 |
-
#kills process if there was one running
|
| 269 |
-
def closeEvent(self, event):
|
| 270 |
-
try:
|
| 271 |
-
self.process.kill()
|
| 272 |
-
self.perc = False
|
| 273 |
-
self.bool_temp = False
|
| 274 |
-
self.running = False
|
| 275 |
-
event.accept()
|
| 276 |
-
except Exception as e:
|
| 277 |
-
show_error("Error in closeEvent() in OffTarget.", e)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -0,0 +1,253 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from models.OffTargetModel import OffTargetModel
|
| 2 |
+
from utils.ui import show_error
|
| 3 |
+
from views.OffTargetView import OffTargetView
|
| 4 |
+
from PyQt6.QtCore import QObject, pyqtSlot, pyqtSignal
|
| 5 |
+
import os
|
| 6 |
+
|
| 7 |
+
class OffTargetController(QObject):
|
| 8 |
+
# Update signal to emit tuple of (scores, details)
|
| 9 |
+
off_target_results_ready = pyqtSignal(tuple) # Emits (scores_dict, details_dict)
|
| 10 |
+
|
| 11 |
+
def __init__(self, global_settings):
|
| 12 |
+
super().__init__()
|
| 13 |
+
self.global_settings = global_settings
|
| 14 |
+
self.logger = global_settings.get_logger()
|
| 15 |
+
|
| 16 |
+
try:
|
| 17 |
+
self.model = OffTargetModel(global_settings)
|
| 18 |
+
self.view = OffTargetView(global_settings)
|
| 19 |
+
|
| 20 |
+
self._init_ui()
|
| 21 |
+
|
| 22 |
+
self._setup_connections()
|
| 23 |
+
|
| 24 |
+
self.global_settings.theme_changed.connect(self.on_theme_changed)
|
| 25 |
+
except Exception as e:
|
| 26 |
+
show_error(self.global_settings, "Error initializing OffTargetController", str(e))
|
| 27 |
+
|
| 28 |
+
def _init_ui(self):
|
| 29 |
+
try:
|
| 30 |
+
organisms = self.model.get_organisms()
|
| 31 |
+
self.view.set_combo_box_organism(organisms)
|
| 32 |
+
|
| 33 |
+
if organisms:
|
| 34 |
+
endonucleases = self.model.get_endonucleases(organisms[0])
|
| 35 |
+
self.view.set_combo_box_endonuclease(endonucleases)
|
| 36 |
+
|
| 37 |
+
self.view.set_combo_box_max_mismatches()
|
| 38 |
+
|
| 39 |
+
except Exception as e:
|
| 40 |
+
self.logger.error(f"Error setting up initial data: {str(e)}")
|
| 41 |
+
self.view.show_error("Setup Error", str(e))
|
| 42 |
+
|
| 43 |
+
def _setup_connections(self):
|
| 44 |
+
try:
|
| 45 |
+
self.view.push_button_submit.clicked.connect(self._on_submit_clicked)
|
| 46 |
+
|
| 47 |
+
# Connect to model's signals
|
| 48 |
+
self.model.results_ready.connect(lambda results: self._on_results_ready(results))
|
| 49 |
+
self.model.progress_updated.connect(self._on_progress_updated)
|
| 50 |
+
|
| 51 |
+
except Exception as e:
|
| 52 |
+
self.logger.error(f"Error connecting signals: {str(e)}")
|
| 53 |
+
|
| 54 |
+
@pyqtSlot(str)
|
| 55 |
+
def _on_organism_changed(self, organism):
|
| 56 |
+
"""Handle organism selection change"""
|
| 57 |
+
try:
|
| 58 |
+
endonucleases = self.model.get_endonucleases(organism)
|
| 59 |
+
self.view.set_endonucleases(endonucleases)
|
| 60 |
+
except Exception as e:
|
| 61 |
+
self.logger.error(f"Error updating endonucleases: {str(e)}")
|
| 62 |
+
|
| 63 |
+
@pyqtSlot()
|
| 64 |
+
def _on_submit_clicked(self):
|
| 65 |
+
"""Handle submit button click"""
|
| 66 |
+
try:
|
| 67 |
+
# Get analysis parameters
|
| 68 |
+
parameters = self.view.get_analysis_parameters()
|
| 69 |
+
|
| 70 |
+
# Add stored targets
|
| 71 |
+
if hasattr(self, '_targets'):
|
| 72 |
+
parameters['targets'] = self._targets
|
| 73 |
+
else:
|
| 74 |
+
raise ValueError("No targets available for analysis")
|
| 75 |
+
|
| 76 |
+
# Validate parameters
|
| 77 |
+
if not self._validate_parameters(parameters):
|
| 78 |
+
return
|
| 79 |
+
|
| 80 |
+
self.model._write_targets_to_temp(parameters['targets'])
|
| 81 |
+
|
| 82 |
+
# Start analysis
|
| 83 |
+
self.model.start_analysis(parameters)
|
| 84 |
+
|
| 85 |
+
# Update UI
|
| 86 |
+
self.view.update_progress_bar(1, "Starting analysis...")
|
| 87 |
+
self.view.push_button_submit.setEnabled(False)
|
| 88 |
+
|
| 89 |
+
except Exception as e:
|
| 90 |
+
self.logger.error(f"Error in submit action: {str(e)}")
|
| 91 |
+
self.view.show_error("Submit Error", str(e))
|
| 92 |
+
|
| 93 |
+
def _validate_parameters(self, parameters):
|
| 94 |
+
"""Validate analysis parameters"""
|
| 95 |
+
try:
|
| 96 |
+
if parameters['save_output']:
|
| 97 |
+
if not parameters['output_filename']:
|
| 98 |
+
self.view.show_warning(
|
| 99 |
+
"Input Error",
|
| 100 |
+
"Please enter a valid output file name."
|
| 101 |
+
)
|
| 102 |
+
return False
|
| 103 |
+
|
| 104 |
+
output_path = os.path.join(
|
| 105 |
+
self.global_settings.get_db_path(),
|
| 106 |
+
parameters['output_filename']
|
| 107 |
+
)
|
| 108 |
+
if os.path.exists(output_path):
|
| 109 |
+
self.view.show_warning(
|
| 110 |
+
"File Error",
|
| 111 |
+
"Output file already exists. Please choose a new name."
|
| 112 |
+
)
|
| 113 |
+
return False
|
| 114 |
+
|
| 115 |
+
return True
|
| 116 |
+
|
| 117 |
+
except Exception as e:
|
| 118 |
+
self.logger.error(f"Error validating parameters: {str(e)}")
|
| 119 |
+
return False
|
| 120 |
+
|
| 121 |
+
@pyqtSlot()
|
| 122 |
+
def _on_cancel_clicked(self):
|
| 123 |
+
"""Handle cancel button click"""
|
| 124 |
+
try:
|
| 125 |
+
self.model.stop_analysis()
|
| 126 |
+
self._cleanup_temp_files()
|
| 127 |
+
self.view.close()
|
| 128 |
+
except Exception as e:
|
| 129 |
+
self.logger.error(f"Error canceling analysis: {str(e)}")
|
| 130 |
+
|
| 131 |
+
def _cleanup_temp_files(self):
|
| 132 |
+
"""Clean up temporary files"""
|
| 133 |
+
try:
|
| 134 |
+
# Get path to temp file
|
| 135 |
+
temp_path = os.path.join(
|
| 136 |
+
self.global_settings.get_off_target_dir_path(),
|
| 137 |
+
'temp.txt'
|
| 138 |
+
)
|
| 139 |
+
|
| 140 |
+
# Clean up temp file
|
| 141 |
+
if os.path.exists(temp_path):
|
| 142 |
+
os.remove(temp_path)
|
| 143 |
+
self.logger.debug(f"Removed temp file: {temp_path}")
|
| 144 |
+
|
| 145 |
+
# Clean up local output file
|
| 146 |
+
local_output = os.path.join(
|
| 147 |
+
self.global_settings.get_off_target_dir_path(),
|
| 148 |
+
'local_output.txt'
|
| 149 |
+
)
|
| 150 |
+
if os.path.exists(local_output):
|
| 151 |
+
os.remove(local_output)
|
| 152 |
+
self.logger.debug(f"Removed local output file: {local_output}")
|
| 153 |
+
|
| 154 |
+
except Exception as e:
|
| 155 |
+
self.logger.error(f"Error cleaning up temp files: {str(e)}")
|
| 156 |
+
|
| 157 |
+
def _on_results_ready(self, results):
|
| 158 |
+
"""Handle results from model"""
|
| 159 |
+
try:
|
| 160 |
+
scores, details = results # Unpack the tuple of results
|
| 161 |
+
if scores: # Make sure we have valid results
|
| 162 |
+
# Store results
|
| 163 |
+
self._off_target_results = scores
|
| 164 |
+
self._off_target_details = details
|
| 165 |
+
|
| 166 |
+
# Emit results signal immediately with both scores and details
|
| 167 |
+
self.off_target_results_ready.emit((scores, details))
|
| 168 |
+
|
| 169 |
+
# Close the window
|
| 170 |
+
self.view.close()
|
| 171 |
+
|
| 172 |
+
self.logger.debug(f"Received and emitted {len(scores)} off-target results with {len(details)} detailed results")
|
| 173 |
+
|
| 174 |
+
else:
|
| 175 |
+
self.logger.warning("Received empty results")
|
| 176 |
+
self.view.show_warning(
|
| 177 |
+
"No Results",
|
| 178 |
+
"No off-target analysis results were generated."
|
| 179 |
+
)
|
| 180 |
+
|
| 181 |
+
except Exception as e:
|
| 182 |
+
self.logger.error(f"Error handling results: {str(e)}")
|
| 183 |
+
self.view.show_error("Results Error", str(e))
|
| 184 |
+
|
| 185 |
+
def show(self):
|
| 186 |
+
"""Show the view and bring to front"""
|
| 187 |
+
# Reset view state before showing
|
| 188 |
+
self.view.prog_bar.setValue(0)
|
| 189 |
+
self.view.push_button_submit.setEnabled(True)
|
| 190 |
+
|
| 191 |
+
# Show and bring window to front
|
| 192 |
+
self.view.show()
|
| 193 |
+
self.view.raise_() # Bring window to front
|
| 194 |
+
self.view.activateWindow() # Give window focus
|
| 195 |
+
self.view.apply_theme()
|
| 196 |
+
|
| 197 |
+
def initialize_analysis(self, parameters):
|
| 198 |
+
"""Initialize analysis with parameters from ViewTargets"""
|
| 199 |
+
try:
|
| 200 |
+
# Reset view state
|
| 201 |
+
self.view.prog_bar.setValue(0)
|
| 202 |
+
self.view.push_button_submit.setEnabled(True)
|
| 203 |
+
|
| 204 |
+
# Reset model state
|
| 205 |
+
if hasattr(self.model, '_current_parameters'):
|
| 206 |
+
delattr(self.model, '_current_parameters')
|
| 207 |
+
if hasattr(self, '_off_target_results'):
|
| 208 |
+
delattr(self, '_off_target_results')
|
| 209 |
+
|
| 210 |
+
# Set organism in view
|
| 211 |
+
organism_index = self.view.combo_box_organism.findText(parameters['organism'])
|
| 212 |
+
if organism_index >= 0:
|
| 213 |
+
self.view.combo_box_organism.setCurrentIndex(organism_index)
|
| 214 |
+
|
| 215 |
+
# Set endonuclease in view
|
| 216 |
+
if 'endonuclease' in parameters:
|
| 217 |
+
endo_index = self.view.combo_box_endonuclease.findText(parameters['endonuclease'])
|
| 218 |
+
if endo_index >= 0:
|
| 219 |
+
self.view.combo_box_endonuclease.setCurrentIndex(endo_index)
|
| 220 |
+
|
| 221 |
+
# Store targets for analysis
|
| 222 |
+
if 'guides' in parameters:
|
| 223 |
+
self._targets = parameters['guides']
|
| 224 |
+
else:
|
| 225 |
+
raise ValueError("No guides provided for analysis")
|
| 226 |
+
|
| 227 |
+
# Reset radio buttons and fields
|
| 228 |
+
self.view.radio_button_average_output_no.setChecked(True)
|
| 229 |
+
self.view.radio_button_save_output_no.setChecked(True)
|
| 230 |
+
self.view.line_edit_output_file.clear()
|
| 231 |
+
self.view.line_edit_output_file.setEnabled(False)
|
| 232 |
+
|
| 233 |
+
# Reset combo box for mismatches
|
| 234 |
+
self.view.set_combo_box_max_mismatches()
|
| 235 |
+
|
| 236 |
+
# Enable submit button
|
| 237 |
+
self.view.push_button_submit.setEnabled(True)
|
| 238 |
+
|
| 239 |
+
except Exception as e:
|
| 240 |
+
self.logger.error(f"Error initializing analysis: {str(e)}")
|
| 241 |
+
self.view.show_error("Initialization Error", str(e))
|
| 242 |
+
|
| 243 |
+
@pyqtSlot(str)
|
| 244 |
+
def on_theme_changed(self, theme):
|
| 245 |
+
"""Handle theme change"""
|
| 246 |
+
self.view.apply_theme()
|
| 247 |
+
|
| 248 |
+
def _on_progress_updated(self, value, status):
|
| 249 |
+
"""Handle progress updates"""
|
| 250 |
+
try:
|
| 251 |
+
self.view.update_progress_bar(value, status)
|
| 252 |
+
except Exception as e:
|
| 253 |
+
self.logger.error(f"Error updating progress: {str(e)}")
|
|
@@ -27,6 +27,9 @@ class PopulationAnalysisWindowController:
|
|
| 27 |
# Tables sorting
|
| 28 |
self.view.table_seed.horizontalHeader().sectionClicked.connect(self.seed_table_sorting)
|
| 29 |
self.view.table_locations.horizontalHeader().sectionClicked.connect(self.loc_table_sorter)
|
|
|
|
|
|
|
|
|
|
| 30 |
except Exception as e:
|
| 31 |
show_error(self.global_settings, "Error setting up connections in population analysis.", str(e))
|
| 32 |
|
|
@@ -100,26 +103,27 @@ class PopulationAnalysisWindowController:
|
|
| 100 |
|
| 101 |
def fill_data(self):
|
| 102 |
try:
|
|
|
|
| 103 |
self.model.seeds = self.model.get_shared_seeds(self.model.db_files, True)
|
| 104 |
|
| 105 |
-
if len(self.model.seeds)
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
else:
|
| 122 |
-
self.logger.warning("
|
| 123 |
|
| 124 |
except Exception as e:
|
| 125 |
show_error(self.global_settings, "Error in fill_data() in population analysis.", str(e))
|
|
@@ -302,3 +306,43 @@ class PopulationAnalysisWindowController:
|
|
| 302 |
event.accept()
|
| 303 |
except Exception as e:
|
| 304 |
show_error(self.global_settings, "Error in closeEvent() in population analysis.", str(e))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
# Tables sorting
|
| 28 |
self.view.table_seed.horizontalHeader().sectionClicked.connect(self.seed_table_sorting)
|
| 29 |
self.view.table_locations.horizontalHeader().sectionClicked.connect(self.loc_table_sorter)
|
| 30 |
+
|
| 31 |
+
# Add new connection for export button
|
| 32 |
+
self.view.push_button_export_selected_gRNAs.clicked.connect(self.export_selected_seeds)
|
| 33 |
except Exception as e:
|
| 34 |
show_error(self.global_settings, "Error setting up connections in population analysis.", str(e))
|
| 35 |
|
|
|
|
| 103 |
|
| 104 |
def fill_data(self):
|
| 105 |
try:
|
| 106 |
+
# Get seeds shared between ALL organisms
|
| 107 |
self.model.seeds = self.model.get_shared_seeds(self.model.db_files, True)
|
| 108 |
|
| 109 |
+
if len(self.model.seeds) > 0:
|
| 110 |
+
# Process seed data for the table
|
| 111 |
+
seed_data = []
|
| 112 |
+
for seed in self.model.seeds:
|
| 113 |
+
data = self.model.get_seed_data(seed, self.model.db_files)
|
| 114 |
+
processed_data = self.process_seed_data(seed, data)
|
| 115 |
+
if processed_data: # Only add if data was processed successfully
|
| 116 |
+
seed_data.append(processed_data)
|
| 117 |
+
|
| 118 |
+
if seed_data: # Only update table if we have data
|
| 119 |
+
self.view.update_shared_seeds_table(seed_data)
|
| 120 |
+
|
| 121 |
+
# Always generate and display heatmap for 2 or more organisms
|
| 122 |
+
if len(self.model.db_files) > 1:
|
| 123 |
+
heatmap_data = self.model.get_heatmap_data(self.model.db_files)
|
| 124 |
+
self.view.plot_heatmap(heatmap_data, self.model.org_names)
|
| 125 |
else:
|
| 126 |
+
self.logger.warning("Not enough organisms selected for heatmap")
|
| 127 |
|
| 128 |
except Exception as e:
|
| 129 |
show_error(self.global_settings, "Error in fill_data() in population analysis.", str(e))
|
|
|
|
| 306 |
event.accept()
|
| 307 |
except Exception as e:
|
| 308 |
show_error(self.global_settings, "Error in closeEvent() in population analysis.", str(e))
|
| 309 |
+
|
| 310 |
+
def export_selected_seeds(self):
|
| 311 |
+
try:
|
| 312 |
+
selected_seeds = []
|
| 313 |
+
selected_rows = self.view.table_seed.selectionModel().selectedRows()
|
| 314 |
+
self.logger.debug(f"Selected rows: {selected_rows}")
|
| 315 |
+
|
| 316 |
+
if not selected_rows:
|
| 317 |
+
show_message(
|
| 318 |
+
fontSize=12,
|
| 319 |
+
icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 320 |
+
title="Nothing Selected",
|
| 321 |
+
message="No seeds were selected. Please select seeds to export."
|
| 322 |
+
)
|
| 323 |
+
return
|
| 324 |
+
|
| 325 |
+
for row_idx in selected_rows:
|
| 326 |
+
seed_data = {
|
| 327 |
+
# Seed, % Coverage, Total Repeats, Avg. Repeats/Scaffold, Consensus Sequence, Full Sequence, % Consensus, Score, PAM, Strand
|
| 328 |
+
# ('TCCCTGGTTCGAATCC', 100.0, 4, 2.0, 'TTGGTCCCTGGTTCGAATCC', 50.0, '55', 'GGG', '-')
|
| 329 |
+
'seed': self.view.table_seed.item(row_idx.row(), 0).text(), # Seed column
|
| 330 |
+
'percent_coverage': self.view.table_seed.item(row_idx.row(), 1).text(), # % Coverage column
|
| 331 |
+
'total_repeats': self.view.table_seed.item(row_idx.row(), 2).text(), # Total Repeats column
|
| 332 |
+
'avg_repeats_or_scaffold': self.view.table_seed.item(row_idx.row(), 3).text(), # Avg. Repeats/Scaffold column
|
| 333 |
+
'consensus_sequence': self.view.table_seed.item(row_idx.row(), 4).text(), # Consensus Sequence column
|
| 334 |
+
'full_sequence': self.view.table_seed.item(row_idx.row(), 4).text(), # Full Sequence column
|
| 335 |
+
'percent_consensus': self.view.table_seed.item(row_idx.row(), 5).text(), # % Consensus column
|
| 336 |
+
'score': self.view.table_seed.item(row_idx.row(), 6).text(), # Score column
|
| 337 |
+
'pam': self.view.table_seed.item(row_idx.row(), 7).text(), # PAM column
|
| 338 |
+
'strand': self.view.table_seed.item(row_idx.row(), 8).text(), # Strand column
|
| 339 |
+
}
|
| 340 |
+
selected_seeds.append(seed_data)
|
| 341 |
+
|
| 342 |
+
# Get export window from global settings and show dialog
|
| 343 |
+
export_window = self.global_settings.get_export_selected_grnas_window()
|
| 344 |
+
print(f"Selected seeds: {selected_seeds}")
|
| 345 |
+
export_window.show_dialog(selected_seeds, "Population Analysis")
|
| 346 |
+
|
| 347 |
+
except Exception as e:
|
| 348 |
+
show_error(self.global_settings, "Error exporting selected seeds", str(e))
|
|
@@ -3,6 +3,7 @@ from PyQt6 import QtWidgets
|
|
| 3 |
from models.StartupWindowModel import StartupWindowModel
|
| 4 |
from utils.ui import show_message, show_error
|
| 5 |
from views.StartupWindowView import StartupWindowView
|
|
|
|
| 6 |
|
| 7 |
class StartupWindowController:
|
| 8 |
def __init__(self, global_settings):
|
|
@@ -66,12 +67,24 @@ class StartupWindowController:
|
|
| 66 |
self.logger.debug(f"Handle go to home or new genome: {self.model.get_db_path()}")
|
| 67 |
is_valid, message = self.settings.validate_db_path(self.model.get_db_path())
|
| 68 |
if is_valid:
|
| 69 |
-
self.settings.set_first_time_startup_completed()
|
| 70 |
-
self.
|
| 71 |
else:
|
| 72 |
self.logger.warning(f"Invalid database path: {message}")
|
| 73 |
self.open_new_genome_tab()
|
| 74 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
def open_new_genome_tab(self):
|
| 76 |
try:
|
| 77 |
self.logger.debug("Opening New Genome tab")
|
|
|
|
| 3 |
from models.StartupWindowModel import StartupWindowModel
|
| 4 |
from utils.ui import show_message, show_error
|
| 5 |
from views.StartupWindowView import StartupWindowView
|
| 6 |
+
import sys
|
| 7 |
|
| 8 |
class StartupWindowController:
|
| 9 |
def __init__(self, global_settings):
|
|
|
|
| 67 |
self.logger.debug(f"Handle go to home or new genome: {self.model.get_db_path()}")
|
| 68 |
is_valid, message = self.settings.validate_db_path(self.model.get_db_path())
|
| 69 |
if is_valid:
|
| 70 |
+
self.settings.set_first_time_startup_completed()
|
| 71 |
+
self.restart_application()
|
| 72 |
else:
|
| 73 |
self.logger.warning(f"Invalid database path: {message}")
|
| 74 |
self.open_new_genome_tab()
|
| 75 |
|
| 76 |
+
def restart_application(self):
|
| 77 |
+
"""Restart the entire application"""
|
| 78 |
+
try:
|
| 79 |
+
self.logger.info("Restarting application...")
|
| 80 |
+
# Get the current application instance
|
| 81 |
+
app = QtWidgets.QApplication.instance()
|
| 82 |
+
# Use a custom exit code for restart (e.g., 1000)
|
| 83 |
+
app.exit(1000) # Changed from QApplication.Exit.ExitCode.Restart
|
| 84 |
+
except Exception as e:
|
| 85 |
+
self.logger.error(f"Error restarting application: {str(e)}", exc_info=True)
|
| 86 |
+
show_error(self.settings, "Error restarting application", str(e))
|
| 87 |
+
|
| 88 |
def open_new_genome_tab(self):
|
| 89 |
try:
|
| 90 |
self.logger.debug("Opening New Genome tab")
|
|
@@ -9,317 +9,565 @@ from PyQt6 import QtWidgets, QtCore
|
|
| 9 |
import traceback
|
| 10 |
import threading
|
| 11 |
from Bio.Seq import Seq
|
|
|
|
| 12 |
|
| 13 |
class ViewTargetsController:
|
| 14 |
def __init__(self, global_settings):
|
| 15 |
-
self.
|
| 16 |
-
start_time = time.time()
|
| 17 |
self.model = ViewTargetsModel(global_settings)
|
| 18 |
self.view = ViewTargetsView(global_settings)
|
| 19 |
-
|
| 20 |
-
self.global_settings.logger.debug(f"ViewTargets initialization took: {init_time:.2f} seconds")
|
| 21 |
|
| 22 |
self.setup_connections()
|
| 23 |
self.organism = ""
|
| 24 |
self.endonuclease = ""
|
|
|
|
| 25 |
|
| 26 |
def setup_connections(self):
|
| 27 |
self.view.push_button_off_target.clicked.connect(self.perform_off_target_analysis)
|
| 28 |
self.view.push_button_cotargeting.clicked.connect(self.perform_cotargeting)
|
| 29 |
self.view.push_button_highlight_guides.clicked.connect(self.highlight_gene_viewer)
|
| 30 |
-
self.view.
|
| 31 |
-
self.view.
|
| 32 |
self.view.push_button_scoring_options.clicked.connect(self.show_scoring_options)
|
| 33 |
self.view.push_button_change_location.clicked.connect(self.change_indices)
|
| 34 |
self.view.push_button_reset_location.clicked.connect(self.reset_location)
|
| 35 |
self.view.check_box_select_all.stateChanged.connect(self.select_all)
|
| 36 |
-
self.view.combo_box_gene.currentIndexChanged.connect(self.display_gene_data)
|
| 37 |
self.view.gene_selected.connect(self.on_gene_selected)
|
|
|
|
|
|
|
|
|
|
| 38 |
|
| 39 |
-
def
|
| 40 |
try:
|
| 41 |
-
total_start = time.time()
|
| 42 |
-
|
| 43 |
self.organism = organism
|
| 44 |
self.endonuclease = endonuclease
|
|
|
|
|
|
|
|
|
|
| 45 |
|
| 46 |
-
|
| 47 |
-
model_start = time.time()
|
| 48 |
-
self.model.load_targets(selected_targets, organism, endonuclease)
|
| 49 |
-
model_time = time.time() - model_start
|
| 50 |
-
self.global_settings.logger.debug(f"Model load_targets took: {model_time:.2f} seconds")
|
| 51 |
-
|
| 52 |
-
# Time getting targets
|
| 53 |
-
targets_start = time.time()
|
| 54 |
-
targets = self.model.get_targets()
|
| 55 |
-
targets_time = time.time() - targets_start
|
| 56 |
-
self.global_settings.logger.debug(f"Getting targets took: {targets_time:.2f} seconds")
|
| 57 |
|
| 58 |
-
# Get
|
| 59 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
|
| 61 |
-
#
|
| 62 |
-
|
| 63 |
-
seen_genes = set()
|
| 64 |
formatted_genes = []
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
|
| 74 |
-
if
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
|
| 86 |
-
|
| 87 |
-
view_start = time.time()
|
| 88 |
self.view.set_combo_box_gene(formatted_genes)
|
| 89 |
-
view_time = time.time() - view_start
|
| 90 |
-
self.global_settings.logger.debug(f"View update took: {view_time:.2f} seconds")
|
| 91 |
-
|
| 92 |
-
genes_time = time.time() - genes_start
|
| 93 |
-
self.global_settings.logger.debug(f"Total setting genes took: {genes_time:.2f} seconds")
|
| 94 |
-
|
| 95 |
-
# Time displaying targets
|
| 96 |
-
display_start = time.time()
|
| 97 |
-
self.view.display_targets_in_table(targets)
|
| 98 |
-
display_time = time.time() - display_start
|
| 99 |
-
self.global_settings.logger.debug(f"Displaying targets took: {display_time:.2f} seconds")
|
| 100 |
-
|
| 101 |
-
# Time setting endonuclease
|
| 102 |
-
endo_start = time.time()
|
| 103 |
-
self.view.set_combo_box_endonuclease([endonuclease])
|
| 104 |
-
endo_time = time.time() - endo_start
|
| 105 |
-
self.global_settings.logger.debug(f"Setting endonuclease took: {endo_time:.2f} seconds")
|
| 106 |
-
|
| 107 |
-
# Time loading gene viewer
|
| 108 |
-
gene_start = time.time()
|
| 109 |
-
self.load_gene_viewer()
|
| 110 |
-
gene_time = time.time() - gene_start
|
| 111 |
-
self.global_settings.logger.debug(f"Loading gene viewer took: {gene_time:.2f} seconds")
|
| 112 |
|
| 113 |
-
|
| 114 |
-
self.
|
| 115 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
except Exception as e:
|
| 117 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
|
| 119 |
def load_gene_viewer(self):
|
| 120 |
-
"""Load gene viewer with sequence and location information"""
|
| 121 |
try:
|
| 122 |
-
total_start = time.time()
|
| 123 |
|
| 124 |
# Get selected gene from combo box
|
| 125 |
-
combo_start = time.time()
|
| 126 |
selected_text = self.view.combo_box_gene.currentText()
|
| 127 |
if not selected_text:
|
| 128 |
-
self.
|
| 129 |
return
|
| 130 |
-
combo_time = time.time() - combo_start
|
| 131 |
-
self.global_settings.logger.debug(f"Combo box access time: {combo_time:.2f} seconds")
|
| 132 |
|
| 133 |
# Extract locus tag from "locus_tag: gene_name" format
|
| 134 |
-
parse_start = time.time()
|
| 135 |
locus_tag = selected_text.split(': ')[0] if ': ' in selected_text else selected_text
|
| 136 |
-
self.
|
| 137 |
-
parse_time = time.time() - parse_start
|
| 138 |
-
self.global_settings.logger.debug(f"Locus tag parsing time: {parse_time:.2f} seconds")
|
| 139 |
|
| 140 |
# Get gene sequence with padding
|
| 141 |
-
sequence_start = time.time()
|
| 142 |
sequence_data = self.model.get_gene_sequence(locus_tag)
|
| 143 |
-
sequence_time = time.time() - sequence_start
|
| 144 |
-
self.global_settings.logger.debug(f"Sequence retrieval time: {sequence_time:.2f} seconds")
|
| 145 |
|
| 146 |
if sequence_data:
|
| 147 |
# Update gene viewer with sequence
|
| 148 |
-
viewer_start = time.time()
|
| 149 |
self.view.set_text_edit_gene_viewer(sequence_data['sequence'])
|
| 150 |
-
viewer_time = time.time() - viewer_start
|
| 151 |
-
self.global_settings.logger.debug(f"Text viewer update time: {viewer_time:.2f} seconds")
|
| 152 |
|
| 153 |
# Update location fields
|
| 154 |
-
location_start = time.time()
|
| 155 |
self.view.line_edit_start_location.setText(str(sequence_data['start']))
|
| 156 |
self.view.line_edit_stop_location.setText(str(sequence_data['end']))
|
| 157 |
-
location_time = time.time() - location_start
|
| 158 |
-
self.global_settings.logger.debug(f"Location fields update time: {location_time:.2f} seconds")
|
| 159 |
|
| 160 |
-
total_time = time.time() - total_start
|
| 161 |
-
self.global_settings.logger.debug(f"Total gene viewer loading took: {total_time:.2f} seconds")
|
| 162 |
else:
|
| 163 |
-
self.
|
| 164 |
|
| 165 |
except Exception as e:
|
| 166 |
-
self.
|
| 167 |
-
self.
|
| 168 |
|
| 169 |
def perform_off_target_analysis(self):
|
|
|
|
| 170 |
try:
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
return
|
| 175 |
-
|
| 176 |
-
#
|
| 177 |
-
|
| 178 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 179 |
except Exception as e:
|
| 180 |
-
|
|
|
|
|
|
|
| 181 |
|
| 182 |
def perform_cotargeting(self):
|
|
|
|
| 183 |
try:
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
return
|
| 188 |
-
|
| 189 |
-
#
|
| 190 |
-
cotargeting_controller = self.
|
| 191 |
-
cotargeting_controller.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 192 |
except Exception as e:
|
| 193 |
-
|
|
|
|
|
|
|
| 194 |
|
| 195 |
def highlight_gene_viewer(self):
|
| 196 |
-
"""Highlight selected
|
| 197 |
try:
|
| 198 |
-
self.
|
| 199 |
|
| 200 |
-
# Get selected
|
| 201 |
-
selected_rows = self.view.
|
| 202 |
-
self.
|
| 203 |
|
| 204 |
if not selected_rows:
|
| 205 |
QMessageBox.warning(self.view, "No Selection",
|
| 206 |
-
"Please select
|
| 207 |
return
|
| 208 |
|
| 209 |
# Convert table selections to the format expected by the model
|
| 210 |
-
|
| 211 |
-
for
|
| 212 |
-
|
| 213 |
-
'location':
|
| 214 |
-
'sequence':
|
| 215 |
-
'strand':
|
| 216 |
}
|
| 217 |
-
|
| 218 |
-
self.
|
| 219 |
|
| 220 |
# Get current gene sequence
|
| 221 |
current_gene = self.view.combo_box_gene.currentText()
|
| 222 |
-
locus_tag = current_gene.split(': ')[0] if ': ' in current_gene else current_gene
|
| 223 |
-
self.global_settings.logger.debug(f"Getting sequence for locus tag: {locus_tag}")
|
| 224 |
|
| 225 |
-
#
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 232 |
|
| 233 |
-
self.
|
| 234 |
|
| 235 |
# Highlight the sequences
|
| 236 |
-
if
|
| 237 |
-
self.
|
| 238 |
-
self.
|
| 239 |
else:
|
| 240 |
-
self.
|
| 241 |
-
QMessageBox.warning(self.view, "No Valid
|
| 242 |
"Could not get sequence information from the selected rows.")
|
| 243 |
|
| 244 |
except Exception as e:
|
| 245 |
-
self.
|
| 246 |
-
show_error(self.
|
| 247 |
|
| 248 |
def export_targets(self):
|
| 249 |
try:
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 253 |
return
|
| 254 |
-
file_path = self.view.get_export_file_path()
|
| 255 |
-
if file_path:
|
| 256 |
-
self.model.export_targets(selected_targets, file_path)
|
| 257 |
-
QMessageBox.information(self.view, "Export Successful", "Selected targets have been exported successfully.")
|
| 258 |
-
except Exception as e:
|
| 259 |
-
show_error(self.global_settings, "Error exporting targets", str(e))
|
| 260 |
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
filter_options = self.model.get_filter_options()
|
| 264 |
-
self.view.show_filter_options_dialog(filter_options)
|
| 265 |
-
if self.view.filter_options_accepted():
|
| 266 |
-
new_options = self.view.get_filter_options()
|
| 267 |
-
self.model.set_filter_options(new_options)
|
| 268 |
-
self.refresh_targets_display()
|
| 269 |
except Exception as e:
|
| 270 |
-
|
|
|
|
|
|
|
| 271 |
|
| 272 |
def show_scoring_options(self):
|
| 273 |
-
"""Show scoring options window"""
|
| 274 |
try:
|
| 275 |
# Create scoring options controller if not exists
|
| 276 |
if not hasattr(self, '_scoring_options_controller'):
|
| 277 |
# Create controller with self as view_targets_controller
|
| 278 |
self._scoring_options_controller = ScoringOptionsController(
|
| 279 |
-
global_settings=self.
|
| 280 |
view_targets_controller=self
|
| 281 |
)
|
| 282 |
|
| 283 |
-
# Show scoring options window
|
| 284 |
self._scoring_options_controller.show()
|
| 285 |
|
| 286 |
except Exception as e:
|
| 287 |
-
self.
|
| 288 |
-
self.
|
| 289 |
-
show_error(self.
|
| 290 |
|
| 291 |
def change_indices(self):
|
|
|
|
| 292 |
try:
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 297 |
else:
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
except Exception as e:
|
| 302 |
-
|
|
|
|
|
|
|
| 303 |
|
| 304 |
def reset_location(self):
|
|
|
|
| 305 |
try:
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 311 |
except Exception as e:
|
| 312 |
-
|
|
|
|
|
|
|
| 313 |
|
| 314 |
def select_all(self, state):
|
| 315 |
try:
|
| 316 |
-
self.view.
|
| 317 |
except Exception as e:
|
| 318 |
-
show_error(self.
|
| 319 |
|
| 320 |
def display_gene_data(self, gene_name):
|
| 321 |
try:
|
| 322 |
gene_data = self.model.get_gene_data(gene_name)
|
|
|
|
| 323 |
if gene_data and gene_data['sequence']:
|
| 324 |
self.view.set_text_edit_gene_viewer(gene_data['sequence'])
|
| 325 |
self.view.line_edit_start_location.setText(str(gene_data['start']))
|
|
@@ -329,14 +577,18 @@ class ViewTargetsController:
|
|
| 329 |
self.view.line_edit_start_location.clear()
|
| 330 |
self.view.line_edit_stop_location.clear()
|
| 331 |
except Exception as e:
|
| 332 |
-
show_error(self.
|
| 333 |
|
| 334 |
-
def
|
|
|
|
| 335 |
try:
|
| 336 |
-
|
| 337 |
-
|
|
|
|
|
|
|
|
|
|
| 338 |
except Exception as e:
|
| 339 |
-
show_error(self.
|
| 340 |
|
| 341 |
def show(self):
|
| 342 |
self.view.show()
|
|
@@ -344,102 +596,156 @@ class ViewTargetsController:
|
|
| 344 |
def on_gene_selected(self, selected_text):
|
| 345 |
"""Handle gene selection signal"""
|
| 346 |
try:
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 362 |
else:
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
self.
|
| 366 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 367 |
|
| 368 |
except Exception as e:
|
| 369 |
-
self.
|
| 370 |
-
self.
|
| 371 |
|
| 372 |
-
def
|
| 373 |
-
"""Highlight selected
|
| 374 |
try:
|
| 375 |
-
self.
|
| 376 |
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
targets_to_highlight = self.view.get_selected_targets()
|
| 380 |
|
| 381 |
-
self.
|
| 382 |
|
| 383 |
-
if not
|
| 384 |
QMessageBox.warning(self.view, "No Selection",
|
| 385 |
-
"Please select
|
| 386 |
return
|
| 387 |
|
| 388 |
# Get current gene sequence
|
| 389 |
selected_text = self.view.combo_box_gene.currentText()
|
| 390 |
-
locus_tag = selected_text.split(': ')[0] if ': ' in selected_text else selected_text
|
| 391 |
|
| 392 |
-
|
| 393 |
-
if
|
| 394 |
-
|
| 395 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 396 |
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
# Sort targets by position for efficient highlighting
|
| 400 |
highlights = []
|
| 401 |
sequences_found = 0
|
| 402 |
-
total_sequences = len(
|
| 403 |
|
| 404 |
-
for
|
| 405 |
-
self.
|
| 406 |
-
sequence_to_find =
|
| 407 |
-
strand =
|
| 408 |
|
| 409 |
-
# For negative strand, we need to use reverse complement
|
| 410 |
if strand == '-':
|
| 411 |
sequence_to_find = str(Seq(sequence_to_find).reverse_complement())
|
| 412 |
-
self.
|
| 413 |
|
| 414 |
-
# Search for the sequence in the gene viewer text
|
| 415 |
sequence_upper = sequence.upper()
|
| 416 |
target_upper = sequence_to_find.upper()
|
| 417 |
|
| 418 |
-
self.
|
| 419 |
|
| 420 |
-
# Find all occurrences
|
| 421 |
pos = sequence_upper.find(target_upper)
|
| 422 |
if pos != -1:
|
| 423 |
-
self.
|
| 424 |
color = 'red' if strand == '-' else 'green'
|
| 425 |
highlights.append((pos, len(sequence_to_find), color))
|
| 426 |
sequences_found += 1
|
| 427 |
else:
|
| 428 |
-
self.
|
| 429 |
|
| 430 |
-
# Only show warning if NO sequences were found
|
| 431 |
if sequences_found == 0:
|
| 432 |
-
self.
|
| 433 |
QMessageBox.warning(self.view, "Highlighting Failed",
|
| 434 |
"Could not highlight any of the selected sequences in the current gene view.")
|
| 435 |
return
|
| 436 |
|
| 437 |
-
self.global_settings.logger.debug(f"Found {sequences_found} out of {total_sequences} sequences to highlight")
|
| 438 |
-
|
| 439 |
# Build highlighted sequence
|
| 440 |
result = []
|
| 441 |
last_pos = 0
|
| 442 |
-
for pos, length, color in sorted(highlights):
|
| 443 |
result.append(sequence[last_pos:pos])
|
| 444 |
result.append(f"<span style='background-color: {color};'>")
|
| 445 |
result.append(sequence[pos:pos+length])
|
|
@@ -451,11 +757,11 @@ class ViewTargetsController:
|
|
| 451 |
|
| 452 |
# Update the view with highlighted sequence
|
| 453 |
self.view.update_gene_viewer(highlighted_sequence)
|
| 454 |
-
self.
|
| 455 |
|
| 456 |
except Exception as e:
|
| 457 |
-
self.
|
| 458 |
-
self.
|
| 459 |
|
| 460 |
def update_scores(self, scores, algorithm):
|
| 461 |
"""Update the table with new scores from alternative scoring methods"""
|
|
@@ -464,9 +770,9 @@ class ViewTargetsController:
|
|
| 464 |
headers = self.view.get_table_headers()
|
| 465 |
|
| 466 |
# Get selected rows
|
| 467 |
-
selected_rows = sorted(set(index.row() for index in self.view.
|
| 468 |
if not selected_rows:
|
| 469 |
-
self.
|
| 470 |
return
|
| 471 |
|
| 472 |
# Determine the position for the new column (after the "Score" column)
|
|
@@ -474,34 +780,32 @@ class ViewTargetsController:
|
|
| 474 |
desired_index = score_index + 1
|
| 475 |
|
| 476 |
# Disable updates to prevent crashes
|
| 477 |
-
self.view.
|
| 478 |
|
| 479 |
try:
|
| 480 |
# Add new column for algorithm if it doesn't exist
|
| 481 |
if algorithm not in headers:
|
| 482 |
-
# Store current column count
|
| 483 |
-
current_cols = self.view.table_targets.columnCount()
|
| 484 |
|
| 485 |
# Insert new column after Score
|
| 486 |
-
self.view.
|
| 487 |
|
| 488 |
# Set header for new column
|
| 489 |
-
self.view.
|
| 490 |
desired_index,
|
| 491 |
QtWidgets.QTableWidgetItem(algorithm)
|
| 492 |
)
|
| 493 |
|
| 494 |
# Move Off-Target and Details columns one position right
|
| 495 |
-
for row in range(self.view.
|
| 496 |
# Move Off-Target
|
| 497 |
-
off_target_item = self.view.
|
| 498 |
if off_target_item:
|
| 499 |
-
self.view.
|
| 500 |
|
| 501 |
# Move Details button
|
| 502 |
-
details_widget = self.view.
|
| 503 |
if details_widget:
|
| 504 |
-
self.view.
|
| 505 |
|
| 506 |
col_index = desired_index
|
| 507 |
else:
|
|
@@ -514,22 +818,180 @@ class ViewTargetsController:
|
|
| 514 |
# Round to 2 decimal places
|
| 515 |
rounded_score = round(float(scores[score_idx]), 2)
|
| 516 |
score_item.setData(QtCore.Qt.ItemDataRole.EditRole, rounded_score)
|
| 517 |
-
self.view.
|
| 518 |
|
| 519 |
-
# Also update the
|
| 520 |
if hasattr(self.view, '_all_results'):
|
| 521 |
self.view._all_results[row]['azimuth_score'] = rounded_score
|
| 522 |
|
| 523 |
# Resize columns to fit new content
|
| 524 |
-
self.view.
|
| 525 |
|
| 526 |
-
self.
|
| 527 |
-
self.
|
| 528 |
|
| 529 |
finally:
|
| 530 |
# Re-enable updates
|
| 531 |
-
self.view.
|
| 532 |
|
| 533 |
except Exception as e:
|
| 534 |
-
self.
|
| 535 |
raise
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
import traceback
|
| 10 |
import threading
|
| 11 |
from Bio.Seq import Seq
|
| 12 |
+
import os
|
| 13 |
|
| 14 |
class ViewTargetsController:
|
| 15 |
def __init__(self, global_settings):
|
| 16 |
+
self.settings = global_settings
|
|
|
|
| 17 |
self.model = ViewTargetsModel(global_settings)
|
| 18 |
self.view = ViewTargetsView(global_settings)
|
| 19 |
+
self.logger = global_settings.get_logger()
|
|
|
|
| 20 |
|
| 21 |
self.setup_connections()
|
| 22 |
self.organism = ""
|
| 23 |
self.endonuclease = ""
|
| 24 |
+
self.selected_targets = None
|
| 25 |
|
| 26 |
def setup_connections(self):
|
| 27 |
self.view.push_button_off_target.clicked.connect(self.perform_off_target_analysis)
|
| 28 |
self.view.push_button_cotargeting.clicked.connect(self.perform_cotargeting)
|
| 29 |
self.view.push_button_highlight_guides.clicked.connect(self.highlight_gene_viewer)
|
| 30 |
+
self.view.push_button_clear_guides.clicked.connect(self.clear_highlighted_guides)
|
| 31 |
+
self.view.push_button_export_selected_grnas.clicked.connect(self.export_targets)
|
| 32 |
self.view.push_button_scoring_options.clicked.connect(self.show_scoring_options)
|
| 33 |
self.view.push_button_change_location.clicked.connect(self.change_indices)
|
| 34 |
self.view.push_button_reset_location.clicked.connect(self.reset_location)
|
| 35 |
self.view.check_box_select_all.stateChanged.connect(self.select_all)
|
| 36 |
+
# self.view.combo_box_gene.currentIndexChanged.connect(self.display_gene_data)
|
| 37 |
self.view.gene_selected.connect(self.on_gene_selected)
|
| 38 |
+
|
| 39 |
+
self.view.check_box_filter_5_prime_g_sequences.stateChanged.connect(self.refresh_guides_display)
|
| 40 |
+
self.view.spin_box_minimum_on_target_score.valueChanged.connect(self.refresh_guides_display)
|
| 41 |
|
| 42 |
+
def load_guides(self, selected_targets, organism, endonuclease):
|
| 43 |
try:
|
|
|
|
|
|
|
| 44 |
self.organism = organism
|
| 45 |
self.endonuclease = endonuclease
|
| 46 |
+
self.selected_targets = selected_targets
|
| 47 |
+
|
| 48 |
+
print(f"Loading guides for {organism} and {selected_targets} with {endonuclease}")
|
| 49 |
|
| 50 |
+
self.model.load_guides(selected_targets, organism, endonuclease)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
|
| 52 |
+
# Get available endonucleases for this organism
|
| 53 |
+
org_to_endo = self.settings.get_organism_to_endonuclease()
|
| 54 |
+
if organism in org_to_endo:
|
| 55 |
+
available_endos = org_to_endo[organism]
|
| 56 |
+
self.view.combo_box_endonuclease.clear()
|
| 57 |
+
self.view.combo_box_endonuclease.addItems(available_endos)
|
| 58 |
+
|
| 59 |
+
# Set current endonuclease
|
| 60 |
+
current_index = self.view.combo_box_endonuclease.findText(endonuclease)
|
| 61 |
+
if current_index >= 0:
|
| 62 |
+
self.view.combo_box_endonuclease.setCurrentIndex(current_index)
|
| 63 |
+
|
| 64 |
+
self.view.combo_box_endonuclease.currentTextChanged.connect(self._on_endonuclease_changed)
|
| 65 |
|
| 66 |
+
# Format gene names for display
|
| 67 |
+
seen_positions = set()
|
|
|
|
| 68 |
formatted_genes = []
|
| 69 |
+
|
| 70 |
+
if selected_targets and selected_targets[0].get('feature_type') == 'Position':
|
| 71 |
+
position_groups = {}
|
| 72 |
+
for target in selected_targets:
|
| 73 |
+
position_name = target['feature_id']
|
| 74 |
+
if position_name not in position_groups:
|
| 75 |
+
position_groups[position_name] = target
|
| 76 |
+
formatted_genes.append(position_name)
|
| 77 |
|
| 78 |
+
if formatted_genes:
|
| 79 |
+
first_guide = position_groups[formatted_genes[0]]
|
| 80 |
+
self.view.line_edit_start_location.setText(str(first_guide['start']))
|
| 81 |
+
self.view.line_edit_stop_location.setText(str(first_guide['end']))
|
| 82 |
+
|
| 83 |
+
if 'gene_sequence' in first_guide:
|
| 84 |
+
self.view.set_text_edit_gene_viewer(first_guide['gene_sequence'])
|
| 85 |
+
else:
|
| 86 |
+
for target in selected_targets:
|
| 87 |
+
gene_name = target.get('feature_name')
|
| 88 |
+
feature_id = target.get('feature_id')
|
| 89 |
+
|
| 90 |
+
if gene_name and feature_id and gene_name not in seen_positions:
|
| 91 |
+
seen_positions.add(gene_name)
|
| 92 |
+
formatted_genes.append(f"{feature_id}: {gene_name}")
|
| 93 |
|
| 94 |
+
formatted_genes.sort()
|
|
|
|
| 95 |
self.view.set_combo_box_gene(formatted_genes)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
|
| 97 |
+
guides = self.model.get_guides()
|
| 98 |
+
self.view.display_guides_in_table(guides)
|
| 99 |
|
| 100 |
+
# Trigger gene sequence retrieval for first entry
|
| 101 |
+
if formatted_genes:
|
| 102 |
+
first_gene = formatted_genes[0]
|
| 103 |
+
self.on_gene_selected(first_gene)
|
| 104 |
+
|
| 105 |
except Exception as e:
|
| 106 |
+
self.logger.error(f"Error in load_guides: {str(e)}")
|
| 107 |
+
self.logger.error(f"Stack trace: {traceback.format_exc()}")
|
| 108 |
+
show_error(self.settings, "Error loading guides", str(e))
|
| 109 |
+
|
| 110 |
+
def _on_endonuclease_changed(self, new_endonuclease):
|
| 111 |
+
try:
|
| 112 |
+
if new_endonuclease != self.endonuclease:
|
| 113 |
+
self.logger.debug(f"Changing endonuclease from {self.endonuclease} to {new_endonuclease}")
|
| 114 |
+
self.endonuclease = new_endonuclease
|
| 115 |
+
|
| 116 |
+
# Check if this is a co-targeting endonuclease combination
|
| 117 |
+
if '|' in new_endonuclease:
|
| 118 |
+
selected_endos = new_endonuclease.split('|')
|
| 119 |
+
# Rerun co-targeting logic with selected endonucleases
|
| 120 |
+
self.handle_cotargeting_result(selected_endos)
|
| 121 |
+
else:
|
| 122 |
+
# Regular single endonuclease handling
|
| 123 |
+
updated_targets = []
|
| 124 |
+
for target in self.selected_targets:
|
| 125 |
+
new_target = target.copy()
|
| 126 |
+
new_target['endonuclease'] = new_endonuclease
|
| 127 |
+
updated_targets.append(new_target)
|
| 128 |
+
|
| 129 |
+
self.logger.debug(f"Created {len(updated_targets)} updated targets for {new_endonuclease}")
|
| 130 |
+
|
| 131 |
+
# Update model with new targets
|
| 132 |
+
self.model.load_guides(updated_targets, self.organism, new_endonuclease)
|
| 133 |
+
guides = self.model.get_guides()
|
| 134 |
+
|
| 135 |
+
self.logger.debug(f"Got {len(guides)} guides from model")
|
| 136 |
+
|
| 137 |
+
# Update display
|
| 138 |
+
self.view.display_guides_in_table(guides)
|
| 139 |
+
|
| 140 |
+
except Exception as e:
|
| 141 |
+
self.logger.error(f"Error changing endonuclease: {str(e)}")
|
| 142 |
+
self.logger.error(f"Stack trace: {traceback.format_exc()}")
|
| 143 |
+
show_error(self.settings, "Error", f"Could not change endonuclease: {str(e)}")
|
| 144 |
|
| 145 |
def load_gene_viewer(self):
|
|
|
|
| 146 |
try:
|
|
|
|
| 147 |
|
| 148 |
# Get selected gene from combo box
|
|
|
|
| 149 |
selected_text = self.view.combo_box_gene.currentText()
|
| 150 |
if not selected_text:
|
| 151 |
+
self.logger.debug("No gene selected")
|
| 152 |
return
|
|
|
|
|
|
|
| 153 |
|
| 154 |
# Extract locus tag from "locus_tag: gene_name" format
|
|
|
|
| 155 |
locus_tag = selected_text.split(': ')[0] if ': ' in selected_text else selected_text
|
| 156 |
+
self.logger.debug(f"Loading sequence for locus tag: {locus_tag}")
|
|
|
|
|
|
|
| 157 |
|
| 158 |
# Get gene sequence with padding
|
|
|
|
| 159 |
sequence_data = self.model.get_gene_sequence(locus_tag)
|
|
|
|
|
|
|
| 160 |
|
| 161 |
if sequence_data:
|
| 162 |
# Update gene viewer with sequence
|
|
|
|
| 163 |
self.view.set_text_edit_gene_viewer(sequence_data['sequence'])
|
|
|
|
|
|
|
| 164 |
|
| 165 |
# Update location fields
|
|
|
|
| 166 |
self.view.line_edit_start_location.setText(str(sequence_data['start']))
|
| 167 |
self.view.line_edit_stop_location.setText(str(sequence_data['end']))
|
|
|
|
|
|
|
| 168 |
|
|
|
|
|
|
|
| 169 |
else:
|
| 170 |
+
self.logger.warning(f"No sequence data found for locus tag {locus_tag}")
|
| 171 |
|
| 172 |
except Exception as e:
|
| 173 |
+
self.logger.error(f"Error in load_gene_viewer: {str(e)}")
|
| 174 |
+
self.logger.error(f"Stack trace: {traceback.format_exc()}")
|
| 175 |
|
| 176 |
def perform_off_target_analysis(self):
|
| 177 |
+
"""Launch off-target analysis for selected guides"""
|
| 178 |
try:
|
| 179 |
+
# Get selected guides
|
| 180 |
+
selected_guides = self.view.get_selected_guides()
|
| 181 |
+
if not selected_guides:
|
| 182 |
+
QtWidgets.QMessageBox.warning(
|
| 183 |
+
self.view,
|
| 184 |
+
"No Selection",
|
| 185 |
+
"Please select guides for off-target analysis."
|
| 186 |
+
)
|
| 187 |
return
|
| 188 |
+
|
| 189 |
+
# Create off-target controller if not exists
|
| 190 |
+
if not hasattr(self, '_off_target_controller'):
|
| 191 |
+
from controllers.OffTargetController import OffTargetController
|
| 192 |
+
self._off_target_controller = OffTargetController(self.settings)
|
| 193 |
+
|
| 194 |
+
# Connect to results signal
|
| 195 |
+
self._off_target_controller.off_target_results_ready.connect(
|
| 196 |
+
self._handle_off_target_results
|
| 197 |
+
)
|
| 198 |
+
|
| 199 |
+
# Set initial parameters based on current organism/endonuclease
|
| 200 |
+
parameters = {
|
| 201 |
+
'organism': self.organism,
|
| 202 |
+
'endonuclease': self.endonuclease,
|
| 203 |
+
'guides': selected_guides # Pass the selected guides
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
# Initialize analysis with parameters
|
| 207 |
+
self._off_target_controller.initialize_analysis(parameters)
|
| 208 |
+
|
| 209 |
+
# Show and bring window to front
|
| 210 |
+
self._off_target_controller.show()
|
| 211 |
+
|
| 212 |
+
except Exception as e:
|
| 213 |
+
self.logger.error(f"Error launching off-target analysis: {str(e)}")
|
| 214 |
+
show_error(self.settings, "Error", f"Could not launch off-target analysis: {str(e)}")
|
| 215 |
+
|
| 216 |
+
def _handle_off_target_results(self, results):
|
| 217 |
+
"""Handle off-target analysis results"""
|
| 218 |
+
try:
|
| 219 |
+
scores, details = results # Unpack the tuple of results
|
| 220 |
+
|
| 221 |
+
# Get current table headers
|
| 222 |
+
headers = self.view.get_table_headers()
|
| 223 |
+
|
| 224 |
+
# Find Score column index
|
| 225 |
+
score_index = headers.index("Score")
|
| 226 |
+
|
| 227 |
+
# Add Off-Target column if it doesn't exist
|
| 228 |
+
if "Off-Target" not in headers:
|
| 229 |
+
self.view.table_guides.insertColumn(score_index + 1)
|
| 230 |
+
self.view.table_guides.setHorizontalHeaderItem(
|
| 231 |
+
score_index + 1,
|
| 232 |
+
QtWidgets.QTableWidgetItem("Off-Target")
|
| 233 |
+
)
|
| 234 |
+
|
| 235 |
+
off_target_index = headers.index("Off-Target") if "Off-Target" in headers else score_index + 1
|
| 236 |
+
|
| 237 |
+
# Update the view with results and details
|
| 238 |
+
self.view.update_off_target_details(scores, details)
|
| 239 |
+
|
| 240 |
+
self.logger.debug(f"Updated off-target scores in table with {len(details) if details else 0} detailed results")
|
| 241 |
+
|
| 242 |
except Exception as e:
|
| 243 |
+
self.logger.error(f"Error handling off-target results: {str(e)}")
|
| 244 |
+
self.logger.error(f"Stack trace: {traceback.format_exc()}")
|
| 245 |
+
show_error(self.settings, "Error", f"Could not update off-target scores: {str(e)}")
|
| 246 |
|
| 247 |
def perform_cotargeting(self):
|
| 248 |
+
"""Launch co-targeting analysis"""
|
| 249 |
try:
|
| 250 |
+
# Get current endonuclease choices
|
| 251 |
+
current_items = [self.view.combo_box_endonuclease.itemText(i)
|
| 252 |
+
for i in range(self.view.combo_box_endonuclease.count())]
|
| 253 |
+
|
| 254 |
+
if len(current_items) <= 1:
|
| 255 |
+
QtWidgets.QMessageBox.warning(
|
| 256 |
+
self.view,
|
| 257 |
+
"Not Enough Endonucleases",
|
| 258 |
+
"There are not enough endonucleases with this organism. At least 2 endonucleases are required for this function."
|
| 259 |
+
)
|
| 260 |
return
|
| 261 |
+
|
| 262 |
+
# Get cotargeting controller and launch, passing self as view_targets_controller
|
| 263 |
+
cotargeting_controller = self.settings.get_cotargeting_window(self)
|
| 264 |
+
cotargeting_controller.launch(
|
| 265 |
+
endo_choices=current_items,
|
| 266 |
+
org_name=self.organism
|
| 267 |
+
)
|
| 268 |
+
|
| 269 |
except Exception as e:
|
| 270 |
+
self.logger.error(f"Error in perform_cotargeting: {str(e)}")
|
| 271 |
+
self.logger.error(f"Stack trace: {traceback.format_exc()}")
|
| 272 |
+
show_error(self.settings, "Error", f"Could not launch co-targeting: {str(e)}")
|
| 273 |
|
| 274 |
def highlight_gene_viewer(self):
|
| 275 |
+
"""Highlight selected guides in gene viewer"""
|
| 276 |
try:
|
| 277 |
+
self.logger.debug("Starting highlight_gene_viewer")
|
| 278 |
|
| 279 |
+
# Get selected guides
|
| 280 |
+
selected_rows = self.view.get_selected_guides()
|
| 281 |
+
self.logger.debug(f"Selected guides: {selected_rows}")
|
| 282 |
|
| 283 |
if not selected_rows:
|
| 284 |
QMessageBox.warning(self.view, "No Selection",
|
| 285 |
+
"Please select guides to highlight in the gene viewer.")
|
| 286 |
return
|
| 287 |
|
| 288 |
# Convert table selections to the format expected by the model
|
| 289 |
+
guides_to_highlight = []
|
| 290 |
+
for guide in selected_rows:
|
| 291 |
+
guide_info = {
|
| 292 |
+
'location': guide['location'],
|
| 293 |
+
'sequence': guide['sequence'],
|
| 294 |
+
'strand': guide['strand']
|
| 295 |
}
|
| 296 |
+
guides_to_highlight.append(guide_info)
|
| 297 |
+
self.logger.debug(f"Guide to highlight: {guide_info}")
|
| 298 |
|
| 299 |
# Get current gene sequence
|
| 300 |
current_gene = self.view.combo_box_gene.currentText()
|
|
|
|
|
|
|
| 301 |
|
| 302 |
+
# Check if this is a position-based search
|
| 303 |
+
if "chrom" in current_gene and "start:" in current_gene:
|
| 304 |
+
# Parse position from the text (format: "chrom X, start: Y, end: Z")
|
| 305 |
+
try:
|
| 306 |
+
parts = current_gene.split(',')
|
| 307 |
+
chrom = int(parts[0].split('chrom')[1].strip())
|
| 308 |
+
start = int(parts[1].split('start:')[1].strip())
|
| 309 |
+
end = int(parts[2].split('end:')[1].strip())
|
| 310 |
+
|
| 311 |
+
# Get sequence directly from model's _get_sequence_for_position
|
| 312 |
+
sequence = self.model._get_sequence_for_position(chrom, start, end)
|
| 313 |
+
if not sequence:
|
| 314 |
+
raise ValueError("Could not get sequence for position")
|
| 315 |
+
|
| 316 |
+
sequence_data = {
|
| 317 |
+
'sequence': sequence,
|
| 318 |
+
'start': start,
|
| 319 |
+
'end': end
|
| 320 |
+
}
|
| 321 |
+
self.logger.debug(f"Got position-based sequence of length: {len(sequence)}")
|
| 322 |
+
except Exception as e:
|
| 323 |
+
self.logger.error(f"Error getting position sequence: {str(e)}")
|
| 324 |
+
QMessageBox.warning(self.view, "Error",
|
| 325 |
+
"Could not get sequence for the specified position.")
|
| 326 |
+
return
|
| 327 |
+
else:
|
| 328 |
+
# Regular gene-based search
|
| 329 |
+
locus_tag = current_gene.split(': ')[0] if ': ' in current_gene else current_gene
|
| 330 |
+
self.logger.debug(f"Getting sequence for locus tag: {locus_tag}")
|
| 331 |
+
|
| 332 |
+
# Get gene sequence with padding
|
| 333 |
+
sequence_data = self.model.get_gene_sequence(locus_tag)
|
| 334 |
+
if not sequence_data or 'sequence' not in sequence_data:
|
| 335 |
+
self.logger.error("No sequence data found")
|
| 336 |
+
QMessageBox.warning(self.view, "No Gene Data",
|
| 337 |
+
"Could not get gene sequence for highlighting.")
|
| 338 |
+
return
|
| 339 |
|
| 340 |
+
self.logger.debug(f"Gene sequence length: {len(sequence_data['sequence'])}")
|
| 341 |
|
| 342 |
# Highlight the sequences
|
| 343 |
+
if guides_to_highlight:
|
| 344 |
+
self.logger.debug("Attempting to highlight sequences")
|
| 345 |
+
self.highlight_guides_in_gene_viewer(guides_to_highlight)
|
| 346 |
else:
|
| 347 |
+
self.logger.error("No valid guides to highlight")
|
| 348 |
+
QMessageBox.warning(self.view, "No Valid Guides",
|
| 349 |
"Could not get sequence information from the selected rows.")
|
| 350 |
|
| 351 |
except Exception as e:
|
| 352 |
+
self.logger.error(f"Error in highlight_gene_viewer: {str(e)}\n{traceback.format_exc()}")
|
| 353 |
+
show_error(self.settings, "Error highlighting gene viewer", str(e))
|
| 354 |
|
| 355 |
def export_targets(self):
|
| 356 |
try:
|
| 357 |
+
# Get selected guides
|
| 358 |
+
selected_guides = self.view.get_selected_guides()
|
| 359 |
+
if not selected_guides:
|
| 360 |
+
QtWidgets.QMessageBox.warning(
|
| 361 |
+
self.view,
|
| 362 |
+
"No Selection",
|
| 363 |
+
"Please select guides to export."
|
| 364 |
+
)
|
| 365 |
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 366 |
|
| 367 |
+
export_controller = self.settings.get_export_selected_grnas_window()
|
| 368 |
+
export_controller.show_dialog(selected_guides, "View Targets")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 369 |
except Exception as e:
|
| 370 |
+
self.logger.error(f"Error in export_targets: {str(e)}")
|
| 371 |
+
self.logger.error(f"Stack trace: {traceback.format_exc()}")
|
| 372 |
+
show_error(self.settings, "Export Error", str(e))
|
| 373 |
|
| 374 |
def show_scoring_options(self):
|
|
|
|
| 375 |
try:
|
| 376 |
# Create scoring options controller if not exists
|
| 377 |
if not hasattr(self, '_scoring_options_controller'):
|
| 378 |
# Create controller with self as view_targets_controller
|
| 379 |
self._scoring_options_controller = ScoringOptionsController(
|
| 380 |
+
global_settings=self.settings,
|
| 381 |
view_targets_controller=self
|
| 382 |
)
|
| 383 |
|
|
|
|
| 384 |
self._scoring_options_controller.show()
|
| 385 |
|
| 386 |
except Exception as e:
|
| 387 |
+
self.logger.error(f"Error showing scoring options: {str(e)}")
|
| 388 |
+
self.logger.error(f"Stack trace: {traceback.format_exc()}")
|
| 389 |
+
show_error(self.settings, "Error", f"Could not show scoring options: {str(e)}")
|
| 390 |
|
| 391 |
def change_indices(self):
|
| 392 |
+
"""Change the start and end positions for gene viewer"""
|
| 393 |
try:
|
| 394 |
+
# Make sure gene viewer has content
|
| 395 |
+
if not self.view.text_edit_gene_viewer.toPlainText():
|
| 396 |
+
QMessageBox.warning(
|
| 397 |
+
self.view,
|
| 398 |
+
"Gene Viewer Error",
|
| 399 |
+
"Gene Viewer display is empty! Please ensure there is sequence data to view."
|
| 400 |
+
)
|
| 401 |
+
return
|
| 402 |
+
|
| 403 |
+
# Get current gene/position info
|
| 404 |
+
current_gene = self.view.combo_box_gene.currentText()
|
| 405 |
+
|
| 406 |
+
try:
|
| 407 |
+
new_start = int(self.view.line_edit_start_location.text())
|
| 408 |
+
new_end = int(self.view.line_edit_stop_location.text())
|
| 409 |
+
except ValueError:
|
| 410 |
+
QMessageBox.warning(
|
| 411 |
+
self.view,
|
| 412 |
+
"Invalid Input",
|
| 413 |
+
"Please enter valid integer values for start and end positions."
|
| 414 |
+
)
|
| 415 |
+
return
|
| 416 |
+
|
| 417 |
+
# Validate start and end positions
|
| 418 |
+
if new_start <= 0 or new_end <= 0:
|
| 419 |
+
QMessageBox.warning(
|
| 420 |
+
self.view,
|
| 421 |
+
"Invalid location indices",
|
| 422 |
+
"Location indices cannot be negative or zero! Please set values larger than 0."
|
| 423 |
+
)
|
| 424 |
+
return
|
| 425 |
+
|
| 426 |
+
if new_start >= new_end:
|
| 427 |
+
QMessageBox.warning(
|
| 428 |
+
self.view,
|
| 429 |
+
"Invalid location indices",
|
| 430 |
+
"Start location must be less than stop location."
|
| 431 |
+
)
|
| 432 |
+
return
|
| 433 |
+
|
| 434 |
+
if abs(new_start - new_end) > 50000:
|
| 435 |
+
QMessageBox.warning(
|
| 436 |
+
self.view,
|
| 437 |
+
"Sequence Too Long",
|
| 438 |
+
"The sequence is too long! Please choose indices that will make the sequence less than 50,000!"
|
| 439 |
+
)
|
| 440 |
+
return
|
| 441 |
+
|
| 442 |
+
# Get sequence for new range
|
| 443 |
+
if "chrom" in current_gene and "start:" in current_gene:
|
| 444 |
+
# For position-based searches
|
| 445 |
+
try:
|
| 446 |
+
parts = current_gene.split(',')
|
| 447 |
+
chrom = int(parts[0].split('chrom')[1].strip())
|
| 448 |
+
|
| 449 |
+
# Get sequence for new range
|
| 450 |
+
sequence = self.model._get_sequence_for_position(chrom, new_start, new_end)
|
| 451 |
+
|
| 452 |
+
if sequence:
|
| 453 |
+
self.view.set_text_edit_gene_viewer(sequence)
|
| 454 |
+
# Update the line edits with new positions
|
| 455 |
+
self.view.line_edit_start_location.setText(str(new_start))
|
| 456 |
+
self.view.line_edit_stop_location.setText(str(new_end))
|
| 457 |
+
else:
|
| 458 |
+
raise ValueError("Could not get sequence for new position")
|
| 459 |
+
|
| 460 |
+
except Exception as e:
|
| 461 |
+
QMessageBox.warning(
|
| 462 |
+
self.view,
|
| 463 |
+
"Position Error",
|
| 464 |
+
f"Error changing position: {str(e)}"
|
| 465 |
+
)
|
| 466 |
+
return
|
| 467 |
else:
|
| 468 |
+
# For feature-based searches
|
| 469 |
+
locus_tag = current_gene.split(': ')[0] if ': ' in current_gene else current_gene
|
| 470 |
+
gene_data = self.model.get_gene_data(locus_tag)
|
| 471 |
+
|
| 472 |
+
if not gene_data or 'info' not in gene_data:
|
| 473 |
+
QMessageBox.warning(
|
| 474 |
+
self.view,
|
| 475 |
+
"Gene Data Error",
|
| 476 |
+
"Could not get gene data for the current selection."
|
| 477 |
+
)
|
| 478 |
+
return
|
| 479 |
+
|
| 480 |
+
# Get new sequence for the range
|
| 481 |
+
sequence_data = self.model.get_gene_sequence_for_range(locus_tag, new_start, new_end)
|
| 482 |
+
if sequence_data:
|
| 483 |
+
self.view.set_text_edit_gene_viewer(sequence_data['sequence'])
|
| 484 |
+
# Update the line edits with new positions
|
| 485 |
+
self.view.line_edit_start_location.setText(str(new_start))
|
| 486 |
+
self.view.line_edit_stop_location.setText(str(new_end))
|
| 487 |
+
else:
|
| 488 |
+
QMessageBox.warning(
|
| 489 |
+
self.view,
|
| 490 |
+
"Sequence Error",
|
| 491 |
+
"Could not get sequence for the specified range."
|
| 492 |
+
)
|
| 493 |
+
return
|
| 494 |
+
|
| 495 |
except Exception as e:
|
| 496 |
+
self.logger.error(f"Error in change_indices: {str(e)}")
|
| 497 |
+
self.logger.error(f"Stack trace: {traceback.format_exc()}")
|
| 498 |
+
show_error(self.settings, "Error changing indices", str(e))
|
| 499 |
|
| 500 |
def reset_location(self):
|
| 501 |
+
"""Reset gene viewer to the original sequence and location"""
|
| 502 |
try:
|
| 503 |
+
# Get current gene/position
|
| 504 |
+
current_gene = self.view.combo_box_gene.currentText()
|
| 505 |
+
|
| 506 |
+
# Check if this is a position-based search
|
| 507 |
+
if "chrom" in current_gene and "start:" in current_gene:
|
| 508 |
+
try:
|
| 509 |
+
# Parse position from the text (format: "chrom X, start: Y, end: Z")
|
| 510 |
+
parts = current_gene.split(',')
|
| 511 |
+
chrom = int(parts[0].split('chrom')[1].strip())
|
| 512 |
+
start = int(parts[1].split('start:')[1].strip())
|
| 513 |
+
end = int(parts[2].split('end:')[1].strip())
|
| 514 |
+
|
| 515 |
+
# Get sequence directly using model's method
|
| 516 |
+
sequence = self.model._get_sequence_for_position(chrom, start, end)
|
| 517 |
+
if sequence:
|
| 518 |
+
# Update gene viewer with sequence
|
| 519 |
+
self.view.set_text_edit_gene_viewer(sequence)
|
| 520 |
+
|
| 521 |
+
# Update location fields
|
| 522 |
+
self.view.line_edit_start_location.setText(str(start))
|
| 523 |
+
self.view.line_edit_stop_location.setText(str(end))
|
| 524 |
+
else:
|
| 525 |
+
raise ValueError("Could not get sequence for position")
|
| 526 |
+
|
| 527 |
+
except Exception as e:
|
| 528 |
+
self.logger.error(f"Error resetting position: {str(e)}")
|
| 529 |
+
QMessageBox.warning(
|
| 530 |
+
self.view,
|
| 531 |
+
"Position Error",
|
| 532 |
+
f"Error resetting position: {str(e)}"
|
| 533 |
+
)
|
| 534 |
+
return
|
| 535 |
+
else:
|
| 536 |
+
# For feature-based searches
|
| 537 |
+
locus_tag = current_gene.split(': ')[0] if ': ' in current_gene else current_gene
|
| 538 |
+
sequence_data = self.model.get_gene_sequence(locus_tag)
|
| 539 |
+
|
| 540 |
+
if sequence_data:
|
| 541 |
+
# Update gene viewer with sequence
|
| 542 |
+
self.view.set_text_edit_gene_viewer(sequence_data['sequence'])
|
| 543 |
+
|
| 544 |
+
# Update location fields
|
| 545 |
+
self.view.line_edit_start_location.setText(str(sequence_data['start']))
|
| 546 |
+
self.view.line_edit_stop_location.setText(str(sequence_data['end']))
|
| 547 |
+
else:
|
| 548 |
+
self.logger.warning(f"No sequence data found for locus tag {locus_tag}")
|
| 549 |
+
QMessageBox.warning(
|
| 550 |
+
self.view,
|
| 551 |
+
"Gene Data Error",
|
| 552 |
+
"Could not get gene sequence for resetting location."
|
| 553 |
+
)
|
| 554 |
+
return
|
| 555 |
+
|
| 556 |
except Exception as e:
|
| 557 |
+
self.logger.error(f"Error in reset_location: {str(e)}")
|
| 558 |
+
self.logger.error(f"Stack trace: {traceback.format_exc()}")
|
| 559 |
+
show_error(self.settings, "Error resetting location", str(e))
|
| 560 |
|
| 561 |
def select_all(self, state):
|
| 562 |
try:
|
| 563 |
+
self.view.select_all_guides(state == 2) # 2 corresponds to Qt.Checked
|
| 564 |
except Exception as e:
|
| 565 |
+
show_error(self.settings, "Error selecting all guides", str(e))
|
| 566 |
|
| 567 |
def display_gene_data(self, gene_name):
|
| 568 |
try:
|
| 569 |
gene_data = self.model.get_gene_data(gene_name)
|
| 570 |
+
print(f"Gene data: {gene_data}")
|
| 571 |
if gene_data and gene_data['sequence']:
|
| 572 |
self.view.set_text_edit_gene_viewer(gene_data['sequence'])
|
| 573 |
self.view.line_edit_start_location.setText(str(gene_data['start']))
|
|
|
|
| 577 |
self.view.line_edit_start_location.clear()
|
| 578 |
self.view.line_edit_stop_location.clear()
|
| 579 |
except Exception as e:
|
| 580 |
+
show_error(self.settings, "Error displaying gene data", str(e))
|
| 581 |
|
| 582 |
+
def refresh_guides_display(self):
|
| 583 |
+
"""Refresh the guides display when filters change"""
|
| 584 |
try:
|
| 585 |
+
if hasattr(self, 'selected_targets'):
|
| 586 |
+
# Get current guides from model
|
| 587 |
+
self.model.load_guides(self.selected_targets, self.organism, self.endonuclease)
|
| 588 |
+
guides = self.model.get_guides()
|
| 589 |
+
self.view.display_guides_in_table(guides)
|
| 590 |
except Exception as e:
|
| 591 |
+
show_error(self.settings, "Error refreshing guides display", str(e))
|
| 592 |
|
| 593 |
def show(self):
|
| 594 |
self.view.show()
|
|
|
|
| 596 |
def on_gene_selected(self, selected_text):
|
| 597 |
"""Handle gene selection signal"""
|
| 598 |
try:
|
| 599 |
+
self.logger.debug(f"Gene selection changed to: {selected_text}")
|
| 600 |
+
|
| 601 |
+
# Check if this is a position-based search
|
| 602 |
+
if "chrom" in selected_text and "start:" in selected_text:
|
| 603 |
+
try:
|
| 604 |
+
# Parse position from the text (format: "chrom X, start: Y, end: Z")
|
| 605 |
+
parts = selected_text.split(',')
|
| 606 |
+
chrom = int(parts[0].split('chrom')[1].strip())
|
| 607 |
+
start = int(parts[1].split('start:')[1].strip())
|
| 608 |
+
end = int(parts[2].split('end:')[1].strip())
|
| 609 |
+
|
| 610 |
+
# Get sequence directly using _get_sequence_for_position
|
| 611 |
+
sequence = self.model._get_sequence_for_position(chrom, start, end)
|
| 612 |
+
if sequence:
|
| 613 |
+
# Update gene viewer with sequence
|
| 614 |
+
self.view.set_text_edit_gene_viewer(sequence)
|
| 615 |
+
|
| 616 |
+
# Update location fields
|
| 617 |
+
self.view.line_edit_start_location.setText(str(start))
|
| 618 |
+
self.view.line_edit_stop_location.setText(str(end))
|
| 619 |
+
|
| 620 |
+
self.logger.debug(f"Updated position view with sequence of length: {len(sequence)}")
|
| 621 |
+
|
| 622 |
+
# Filter guides for this position
|
| 623 |
+
position_guides = [g for g in self.model.guides
|
| 624 |
+
if g.get('feature_id') == selected_text]
|
| 625 |
+
self.view.display_guides_in_table(position_guides)
|
| 626 |
+
else:
|
| 627 |
+
self.logger.warning(f"No sequence found for position {chrom}:{start}-{end}")
|
| 628 |
+
self.view.set_text_edit_gene_viewer("No sequence data available for this position")
|
| 629 |
+
self.view.line_edit_start_location.clear()
|
| 630 |
+
self.view.line_edit_stop_location.clear()
|
| 631 |
+
except Exception as e:
|
| 632 |
+
self.logger.error(f"Error handling position selection: {str(e)}")
|
| 633 |
+
self.logger.error(f"Stack trace: {traceback.format_exc()}")
|
| 634 |
else:
|
| 635 |
+
# Regular gene-based search
|
| 636 |
+
locus_tag = selected_text.split(': ')[0] if ': ' in selected_text else selected_text
|
| 637 |
+
self.logger.debug(f"Loading sequence for locus tag: {locus_tag}")
|
| 638 |
+
|
| 639 |
+
# Get gene sequence with padding using locus tag
|
| 640 |
+
sequence_data = self.model.get_gene_sequence(locus_tag)
|
| 641 |
+
if sequence_data:
|
| 642 |
+
# Update gene viewer with sequence
|
| 643 |
+
self.view.set_text_edit_gene_viewer(sequence_data['sequence'])
|
| 644 |
+
|
| 645 |
+
# Update location fields
|
| 646 |
+
self.view.line_edit_start_location.setText(str(sequence_data['start']))
|
| 647 |
+
self.view.line_edit_stop_location.setText(str(sequence_data['end']))
|
| 648 |
+
|
| 649 |
+
self.logger.debug(f"Updated gene viewer with sequence of length: {len(sequence_data['sequence'])}")
|
| 650 |
+
|
| 651 |
+
# Filter guides for this gene
|
| 652 |
+
gene_guides = [g for g in self.model.guides
|
| 653 |
+
if str(g.get('feature_id', '')).strip().lower() == locus_tag.lower()]
|
| 654 |
+
self.view.display_guides_in_table(gene_guides)
|
| 655 |
+
else:
|
| 656 |
+
self.logger.warning(f"No sequence data found for locus tag {locus_tag}")
|
| 657 |
+
self.view.set_text_edit_gene_viewer("No sequence data available for this gene")
|
| 658 |
+
self.view.line_edit_start_location.clear()
|
| 659 |
+
self.view.line_edit_stop_location.clear()
|
| 660 |
|
| 661 |
except Exception as e:
|
| 662 |
+
self.logger.error(f"Error handling gene selection: {str(e)}")
|
| 663 |
+
self.logger.error(f"Stack trace: {traceback.format_exc()}")
|
| 664 |
|
| 665 |
+
def highlight_guides_in_gene_viewer(self, guides_to_highlight=None):
|
| 666 |
+
"""Highlight selected guides in gene viewer"""
|
| 667 |
try:
|
| 668 |
+
self.logger.debug("Starting highlight_gene_viewer")
|
| 669 |
|
| 670 |
+
if guides_to_highlight is None:
|
| 671 |
+
guides_to_highlight = self.view.get_selected_guides()
|
|
|
|
| 672 |
|
| 673 |
+
self.logger.debug(f"Selected guides: {guides_to_highlight}")
|
| 674 |
|
| 675 |
+
if not guides_to_highlight:
|
| 676 |
QMessageBox.warning(self.view, "No Selection",
|
| 677 |
+
"Please select guides to highlight in the gene viewer.")
|
| 678 |
return
|
| 679 |
|
| 680 |
# Get current gene sequence
|
| 681 |
selected_text = self.view.combo_box_gene.currentText()
|
|
|
|
| 682 |
|
| 683 |
+
# For position-based searches, get sequence directly from model
|
| 684 |
+
if "chrom" in selected_text and "start:" in selected_text:
|
| 685 |
+
try:
|
| 686 |
+
# Parse position from the text (format: "chrom X, start: Y, end: Z")
|
| 687 |
+
parts = selected_text.split(',')
|
| 688 |
+
chrom = int(parts[0].split('chrom')[1].strip())
|
| 689 |
+
start = int(parts[1].split('start:')[1].strip())
|
| 690 |
+
end = int(parts[2].split('end:')[1].strip())
|
| 691 |
+
|
| 692 |
+
# Get sequence directly from FindTargetsModel
|
| 693 |
+
sequence = self.model._get_sequence_for_position(chrom, start, end)
|
| 694 |
+
if not sequence:
|
| 695 |
+
raise ValueError("Could not get sequence for position")
|
| 696 |
+
|
| 697 |
+
self.logger.debug(f"Got sequence of length {len(sequence)} for position-based search")
|
| 698 |
+
|
| 699 |
+
except Exception as e:
|
| 700 |
+
self.logger.error(f"Error parsing position or getting sequence: {str(e)}")
|
| 701 |
+
return
|
| 702 |
+
else:
|
| 703 |
+
# Regular gene-based search
|
| 704 |
+
locus_tag = selected_text.split(': ')[0] if ': ' in selected_text else selected_text
|
| 705 |
+
sequence_data = self.model.get_gene_sequence(locus_tag)
|
| 706 |
+
if not sequence_data or 'sequence' not in sequence_data:
|
| 707 |
+
self.logger.error("No sequence data available for highlighting")
|
| 708 |
+
return
|
| 709 |
+
sequence = sequence_data['sequence']
|
| 710 |
|
| 711 |
+
# Process highlights
|
|
|
|
|
|
|
| 712 |
highlights = []
|
| 713 |
sequences_found = 0
|
| 714 |
+
total_sequences = len(guides_to_highlight)
|
| 715 |
|
| 716 |
+
for guide in guides_to_highlight:
|
| 717 |
+
self.logger.debug(f"Processing guide: {guide}")
|
| 718 |
+
sequence_to_find = guide['sequence']
|
| 719 |
+
strand = guide['strand']
|
| 720 |
|
|
|
|
| 721 |
if strand == '-':
|
| 722 |
sequence_to_find = str(Seq(sequence_to_find).reverse_complement())
|
| 723 |
+
self.logger.debug(f"Reverse complemented sequence: {sequence_to_find}")
|
| 724 |
|
|
|
|
| 725 |
sequence_upper = sequence.upper()
|
| 726 |
target_upper = sequence_to_find.upper()
|
| 727 |
|
| 728 |
+
self.logger.debug(f"Searching for sequence: {target_upper}")
|
| 729 |
|
|
|
|
| 730 |
pos = sequence_upper.find(target_upper)
|
| 731 |
if pos != -1:
|
| 732 |
+
self.logger.debug(f"Found sequence at position: {pos}")
|
| 733 |
color = 'red' if strand == '-' else 'green'
|
| 734 |
highlights.append((pos, len(sequence_to_find), color))
|
| 735 |
sequences_found += 1
|
| 736 |
else:
|
| 737 |
+
self.logger.debug(f"Sequence not found: {target_upper}")
|
| 738 |
|
|
|
|
| 739 |
if sequences_found == 0:
|
| 740 |
+
self.logger.warning("No sequences could be highlighted")
|
| 741 |
QMessageBox.warning(self.view, "Highlighting Failed",
|
| 742 |
"Could not highlight any of the selected sequences in the current gene view.")
|
| 743 |
return
|
| 744 |
|
|
|
|
|
|
|
| 745 |
# Build highlighted sequence
|
| 746 |
result = []
|
| 747 |
last_pos = 0
|
| 748 |
+
for pos, length, color in sorted(highlights):
|
| 749 |
result.append(sequence[last_pos:pos])
|
| 750 |
result.append(f"<span style='background-color: {color};'>")
|
| 751 |
result.append(sequence[pos:pos+length])
|
|
|
|
| 757 |
|
| 758 |
# Update the view with highlighted sequence
|
| 759 |
self.view.update_gene_viewer(highlighted_sequence)
|
| 760 |
+
self.logger.debug(f"Successfully highlighted {sequences_found} sequences")
|
| 761 |
|
| 762 |
except Exception as e:
|
| 763 |
+
self.logger.error(f"Error highlighting guides: {str(e)}")
|
| 764 |
+
self.logger.error(f"Stack trace: {traceback.format_exc()}")
|
| 765 |
|
| 766 |
def update_scores(self, scores, algorithm):
|
| 767 |
"""Update the table with new scores from alternative scoring methods"""
|
|
|
|
| 770 |
headers = self.view.get_table_headers()
|
| 771 |
|
| 772 |
# Get selected rows
|
| 773 |
+
selected_rows = sorted(set(index.row() for index in self.view.table_guides.selectedIndexes()))
|
| 774 |
if not selected_rows:
|
| 775 |
+
self.logger.warning("No rows selected for scoring")
|
| 776 |
return
|
| 777 |
|
| 778 |
# Determine the position for the new column (after the "Score" column)
|
|
|
|
| 780 |
desired_index = score_index + 1
|
| 781 |
|
| 782 |
# Disable updates to prevent crashes
|
| 783 |
+
self.view.table_guides.setUpdatesEnabled(False)
|
| 784 |
|
| 785 |
try:
|
| 786 |
# Add new column for algorithm if it doesn't exist
|
| 787 |
if algorithm not in headers:
|
|
|
|
|
|
|
| 788 |
|
| 789 |
# Insert new column after Score
|
| 790 |
+
self.view.table_guides.insertColumn(desired_index)
|
| 791 |
|
| 792 |
# Set header for new column
|
| 793 |
+
self.view.table_guides.setHorizontalHeaderItem(
|
| 794 |
desired_index,
|
| 795 |
QtWidgets.QTableWidgetItem(algorithm)
|
| 796 |
)
|
| 797 |
|
| 798 |
# Move Off-Target and Details columns one position right
|
| 799 |
+
for row in range(self.view.table_guides.rowCount()):
|
| 800 |
# Move Off-Target
|
| 801 |
+
off_target_item = self.view.table_guides.takeItem(row, desired_index)
|
| 802 |
if off_target_item:
|
| 803 |
+
self.view.table_guides.setItem(row, desired_index + 1, off_target_item)
|
| 804 |
|
| 805 |
# Move Details button
|
| 806 |
+
details_widget = self.view.table_guides.cellWidget(row, desired_index)
|
| 807 |
if details_widget:
|
| 808 |
+
self.view.table_guides.setCellWidget(row, desired_index + 1, details_widget)
|
| 809 |
|
| 810 |
col_index = desired_index
|
| 811 |
else:
|
|
|
|
| 818 |
# Round to 2 decimal places
|
| 819 |
rounded_score = round(float(scores[score_idx]), 2)
|
| 820 |
score_item.setData(QtCore.Qt.ItemDataRole.EditRole, rounded_score)
|
| 821 |
+
self.view.table_guides.setItem(row, col_index, score_item)
|
| 822 |
|
| 823 |
+
# Also update the guide data to preserve score during filtering/sorting
|
| 824 |
if hasattr(self.view, '_all_results'):
|
| 825 |
self.view._all_results[row]['azimuth_score'] = rounded_score
|
| 826 |
|
| 827 |
# Resize columns to fit new content
|
| 828 |
+
self.view.table_guides.resizeColumnsToContents()
|
| 829 |
|
| 830 |
+
self.logger.debug(f"Updated scores for algorithm: {algorithm}")
|
| 831 |
+
self.logger.debug(f"Updated rows: {selected_rows}")
|
| 832 |
|
| 833 |
finally:
|
| 834 |
# Re-enable updates
|
| 835 |
+
self.view.table_guides.setUpdatesEnabled(True)
|
| 836 |
|
| 837 |
except Exception as e:
|
| 838 |
+
self.logger.error(f"Error updating scores: {str(e)}")
|
| 839 |
raise
|
| 840 |
+
|
| 841 |
+
def clear_highlighted_guides(self):
|
| 842 |
+
"""Clear highlights in gene viewer and unselect rows in target table"""
|
| 843 |
+
try:
|
| 844 |
+
# Get current gene/position
|
| 845 |
+
current_gene = self.view.combo_box_gene.currentText()
|
| 846 |
+
|
| 847 |
+
# Reset gene viewer to original sequence
|
| 848 |
+
if "chrom" in current_gene and "start:" in current_gene:
|
| 849 |
+
# For position-based searches
|
| 850 |
+
try:
|
| 851 |
+
parts = current_gene.split(',')
|
| 852 |
+
chrom = int(parts[0].split('chrom')[1].strip())
|
| 853 |
+
start = int(parts[1].split('start:')[1].strip())
|
| 854 |
+
end = int(parts[2].split('end:')[1].strip())
|
| 855 |
+
|
| 856 |
+
sequence = self.model._get_sequence_for_position(chrom, start, end)
|
| 857 |
+
if sequence:
|
| 858 |
+
self.view.set_text_edit_gene_viewer(sequence)
|
| 859 |
+
except Exception as e:
|
| 860 |
+
self.logger.error(f"Error resetting position sequence: {str(e)}")
|
| 861 |
+
else:
|
| 862 |
+
# For feature-based searches
|
| 863 |
+
locus_tag = current_gene.split(': ')[0] if ': ' in current_gene else current_gene
|
| 864 |
+
sequence_data = self.model.get_gene_sequence(locus_tag)
|
| 865 |
+
if sequence_data and 'sequence' in sequence_data:
|
| 866 |
+
self.view.set_text_edit_gene_viewer(sequence_data['sequence'])
|
| 867 |
+
|
| 868 |
+
# Clear selected rows in table
|
| 869 |
+
self.view.table_guides.clearSelection()
|
| 870 |
+
|
| 871 |
+
# Uncheck select all checkbox
|
| 872 |
+
self.view.check_box_select_all.setChecked(False)
|
| 873 |
+
|
| 874 |
+
self.logger.debug("Cleared highlighted guides and selections")
|
| 875 |
+
|
| 876 |
+
except Exception as e:
|
| 877 |
+
self.logger.error(f"Error clearing highlighted guides: {str(e)}")
|
| 878 |
+
self.logger.error(f"Stack trace: {traceback.format_exc()}")
|
| 879 |
+
show_error(self.settings, "Error clearing guides", str(e))
|
| 880 |
+
|
| 881 |
+
def handle_cotargeting_result(self, selected_endos):
|
| 882 |
+
try:
|
| 883 |
+
# Format combined endonuclease string
|
| 884 |
+
endo_string = "|".join(selected_endos)
|
| 885 |
+
|
| 886 |
+
# Add combined choice to endonuclease combo box
|
| 887 |
+
current_items = [self.view.combo_box_endonuclease.itemText(i)
|
| 888 |
+
for i in range(self.view.combo_box_endonuclease.count())]
|
| 889 |
+
|
| 890 |
+
if endo_string not in current_items:
|
| 891 |
+
self.view.combo_box_endonuclease.insertItem(0, endo_string)
|
| 892 |
+
|
| 893 |
+
# Set as current selection
|
| 894 |
+
self.view.combo_box_endonuclease.setCurrentText(endo_string)
|
| 895 |
+
|
| 896 |
+
# Define PAM compatibility rules
|
| 897 |
+
pam_rules = {
|
| 898 |
+
'SpCas9': 'NGG',
|
| 899 |
+
'SaCas9': 'NNGRRT',
|
| 900 |
+
'NmCas9': 'NNNNGATT',
|
| 901 |
+
'St1Cas9': 'NNAGAAW',
|
| 902 |
+
'St3Cas9': 'NGGNG',
|
| 903 |
+
'TdCas9': 'NAAAAC',
|
| 904 |
+
'CjCas9': 'NNNNRYAC'
|
| 905 |
+
}
|
| 906 |
+
|
| 907 |
+
# Function to check if a PAM sequence matches the required pattern
|
| 908 |
+
def is_pam_compatible(pam_seq, pattern):
|
| 909 |
+
if len(pam_seq) != len(pattern):
|
| 910 |
+
return False
|
| 911 |
+
|
| 912 |
+
for p, t in zip(pam_seq.upper(), pattern.upper()):
|
| 913 |
+
if t == 'N':
|
| 914 |
+
continue
|
| 915 |
+
elif t == 'R' and p not in 'AG':
|
| 916 |
+
return False
|
| 917 |
+
elif t == 'Y' and p not in 'CT':
|
| 918 |
+
return False
|
| 919 |
+
elif t == 'W' and p not in 'AT':
|
| 920 |
+
return False
|
| 921 |
+
elif t != p and t != 'N':
|
| 922 |
+
return False
|
| 923 |
+
return True
|
| 924 |
+
|
| 925 |
+
# Get guides for each individual endonuclease
|
| 926 |
+
all_guides = []
|
| 927 |
+
for endo in selected_endos:
|
| 928 |
+
# Update guides with current endonuclease
|
| 929 |
+
updated_guides = []
|
| 930 |
+
for guide in self.selected_targets:
|
| 931 |
+
new_guide = guide.copy()
|
| 932 |
+
new_guide['endonuclease'] = endo
|
| 933 |
+
|
| 934 |
+
# Extract chromosome number from full identifier
|
| 935 |
+
if 'chromosome' in new_guide:
|
| 936 |
+
chrom_id = new_guide['chromosome']
|
| 937 |
+
if isinstance(chrom_id, str) and '.' in chrom_id:
|
| 938 |
+
chrom_num = chrom_id.split('.')[-1]
|
| 939 |
+
new_guide['chromosome'] = chrom_num
|
| 940 |
+
|
| 941 |
+
updated_guides.append(new_guide)
|
| 942 |
+
|
| 943 |
+
# Load guides for this endonuclease
|
| 944 |
+
self.model.load_guides(updated_guides, self.organism, endo)
|
| 945 |
+
guides = self.model.get_guides()
|
| 946 |
+
all_guides.extend(guides)
|
| 947 |
+
|
| 948 |
+
# Group guides by sequence and collect their PAMs
|
| 949 |
+
sequence_groups = {}
|
| 950 |
+
for guide in all_guides:
|
| 951 |
+
seq = guide['sequence']
|
| 952 |
+
if seq not in sequence_groups:
|
| 953 |
+
sequence_groups[seq] = {'guides': [], 'pams': set(), 'endos': set()}
|
| 954 |
+
sequence_groups[seq]['guides'].append(guide)
|
| 955 |
+
sequence_groups[seq]['pams'].add(guide['pam'])
|
| 956 |
+
sequence_groups[seq]['endos'].add(guide['endonuclease'])
|
| 957 |
+
|
| 958 |
+
# Filter for guides that have compatible PAMs across all endonucleases
|
| 959 |
+
cotargeted_guides = []
|
| 960 |
+
for seq_info in sequence_groups.values():
|
| 961 |
+
if len(seq_info['endos']) == len(selected_endos):
|
| 962 |
+
# Get the most stringent PAM from the guides
|
| 963 |
+
guide_pams = list(seq_info['pams'])
|
| 964 |
+
|
| 965 |
+
# Sort PAMs by length (longer PAMs are typically more stringent)
|
| 966 |
+
guide_pams.sort(key=len, reverse=True)
|
| 967 |
+
stringent_pam = guide_pams[0]
|
| 968 |
+
|
| 969 |
+
# Check if this PAM is compatible with all selected endonucleases
|
| 970 |
+
is_compatible = True
|
| 971 |
+
for endo in selected_endos:
|
| 972 |
+
for cas9_type, pam_pattern in pam_rules.items():
|
| 973 |
+
if cas9_type in endo:
|
| 974 |
+
if not is_pam_compatible(stringent_pam, pam_pattern):
|
| 975 |
+
is_compatible = False
|
| 976 |
+
break
|
| 977 |
+
if not is_compatible:
|
| 978 |
+
break
|
| 979 |
+
|
| 980 |
+
if is_compatible:
|
| 981 |
+
# Use the guide with the most stringent PAM
|
| 982 |
+
for guide in seq_info['guides']:
|
| 983 |
+
if guide['pam'] == stringent_pam:
|
| 984 |
+
combined_guide = guide.copy()
|
| 985 |
+
combined_guide['endonuclease'] = endo_string
|
| 986 |
+
cotargeted_guides.append(combined_guide)
|
| 987 |
+
break
|
| 988 |
+
|
| 989 |
+
# Update display with co-targeted guides
|
| 990 |
+
self.view.display_guides_in_table(cotargeted_guides)
|
| 991 |
+
|
| 992 |
+
self.logger.debug(f"Found {len(cotargeted_guides)} co-targeted guides with compatible PAMs")
|
| 993 |
+
|
| 994 |
+
except Exception as e:
|
| 995 |
+
self.logger.error(f"Error handling co-targeting result: {str(e)}")
|
| 996 |
+
self.logger.error(f"Stack trace: {traceback.format_exc()}")
|
| 997 |
+
show_error(self.settings, "Co-targeting Error", str(e))
|
|
@@ -1,929 +0,0 @@
|
|
| 1 |
-
from PyQt5 import QtWidgets, Qt, QtGui, QtCore, uic
|
| 2 |
-
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
|
| 3 |
-
from matplotlib.figure import Figure
|
| 4 |
-
import models.GlobalSettings as GlobalSettings
|
| 5 |
-
import os
|
| 6 |
-
import utils.sequence_utils as sequence_utils
|
| 7 |
-
import numpy as np
|
| 8 |
-
from PyQt5.QtWidgets import *
|
| 9 |
-
import gzip
|
| 10 |
-
import sqlite3
|
| 11 |
-
import itertools
|
| 12 |
-
import matplotlib.patches as patches
|
| 13 |
-
import mplcursors
|
| 14 |
-
import copy
|
| 15 |
-
from utils.ui import show_error, scale_ui, center_ui, show_message
|
| 16 |
-
|
| 17 |
-
logger = GlobalSettings.logger
|
| 18 |
-
|
| 19 |
-
class Pop_Analysis(QtWidgets.QMainWindow):
|
| 20 |
-
def __init__(self):
|
| 21 |
-
try:
|
| 22 |
-
super(Pop_Analysis, self).__init__()
|
| 23 |
-
uic.loadUi(GlobalSettings.appdir + 'ui/pop.ui', self)
|
| 24 |
-
self.setWindowIcon(Qt.QIcon(GlobalSettings.appdir + "cas9image.ico"))
|
| 25 |
-
self.goBackButton.clicked.connect(self.go_back)
|
| 26 |
-
self.analyze_button.clicked.connect(self.pre_analyze)
|
| 27 |
-
self.clear_Button.clicked.connect(self.clear)
|
| 28 |
-
self.export_button.clicked.connect(self.export_tool)
|
| 29 |
-
self.sq = sequence_utils.SeqTranslate()
|
| 30 |
-
self.ref_para_list = list()
|
| 31 |
-
self.mode = 0
|
| 32 |
-
self.find_locs_button.clicked.connect(self.find_locations)
|
| 33 |
-
self.clear_loc_button.clicked.connect(self.clear_loc_table)
|
| 34 |
-
self.names = []
|
| 35 |
-
self.names_venn = []
|
| 36 |
-
|
| 37 |
-
""" Colormap Graph initialization """
|
| 38 |
-
self.colormap_layout = QtWidgets.QVBoxLayout()
|
| 39 |
-
self.colormap_layout.setContentsMargins(0,0,0,0)
|
| 40 |
-
self.colormap_canvas = MplCanvas(self) ###Initialize new Canvas
|
| 41 |
-
|
| 42 |
-
groupbox_style = """
|
| 43 |
-
QGroupBox:title{subcontrol-origin: margin;
|
| 44 |
-
left: 10px;
|
| 45 |
-
padding: 0 5px 0 15px;}
|
| 46 |
-
QGroupBox#groupBox{border: 2px solid rgb(111,181,110);
|
| 47 |
-
border-radius: 9px;
|
| 48 |
-
font: bold 14pt 'Arial';
|
| 49 |
-
margin-top: 10px;}"""
|
| 50 |
-
self.groupBox.setStyleSheet(groupbox_style)
|
| 51 |
-
self.groupBox_2.setStyleSheet(groupbox_style.replace("groupBox","groupBox_2"))
|
| 52 |
-
|
| 53 |
-
#organism table
|
| 54 |
-
self.org_Table.setColumnCount(1)
|
| 55 |
-
self.org_Table.setShowGrid(False)
|
| 56 |
-
self.org_Table.setHorizontalHeaderLabels(["Organism"])
|
| 57 |
-
self.org_Table.horizontalHeader().setSectionsClickable(True)
|
| 58 |
-
self.org_Table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
| 59 |
-
self.org_Table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
| 60 |
-
self.org_Table.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
|
| 61 |
-
self.org_Table.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
|
| 62 |
-
#self.org_Table.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
|
| 63 |
-
|
| 64 |
-
#top-right table
|
| 65 |
-
self.table2.setColumnCount(9)
|
| 66 |
-
self.table2.setShowGrid(False)
|
| 67 |
-
self.table2.setHorizontalHeaderLabels(["Seed","% Coverage","Total Repeats","Avg. Repeats/Scaffold", "Consensus Sequence", "% Consensus", "Score","PAM", "Strand"])
|
| 68 |
-
self.table2.horizontalHeader().setSectionsClickable(True)
|
| 69 |
-
self.table2.horizontalHeader().sectionClicked.connect(self.table2_sorting)
|
| 70 |
-
self.table2.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
|
| 71 |
-
self.table2.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
|
| 72 |
-
self.table2.setSelectionBehavior(QtWidgets.QTableView.SelectRows)
|
| 73 |
-
self.table2.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
|
| 74 |
-
self.table2.resizeColumnsToContents()
|
| 75 |
-
|
| 76 |
-
# Finder table
|
| 77 |
-
self.loc_finder_table.setColumnCount(5)
|
| 78 |
-
self.loc_finder_table.setShowGrid(False)
|
| 79 |
-
self.loc_finder_table.setHorizontalHeaderLabels(["Seed ID", "Sequence", "Organism", "Scaffold", "Location"])
|
| 80 |
-
self.loc_finder_table.horizontalHeader().setSectionsClickable(True)
|
| 81 |
-
self.loc_finder_table.horizontalHeader().sectionClicked.connect(self.loc_table_sorter)
|
| 82 |
-
self.loc_finder_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
|
| 83 |
-
self.loc_finder_table.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
|
| 84 |
-
#self.loc_finder_table.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
|
| 85 |
-
# self.loc_finder_table.setSelectionBehavior(QtWidgets.QTableView.SelectRows)
|
| 86 |
-
# self.loc_finder_table.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
|
| 87 |
-
self.loc_finder_table.resizeColumnsToContents()
|
| 88 |
-
|
| 89 |
-
#custom seed search
|
| 90 |
-
self.query_seed_button.clicked.connect(self.custom_seed_search)
|
| 91 |
-
|
| 92 |
-
self.total_org_number = 0
|
| 93 |
-
|
| 94 |
-
self.switcher_table2 = [1, 1, 1, 1, 1, 1, 1, 1,
|
| 95 |
-
1] # for keeping track of where we are in the sorting clicking for each column
|
| 96 |
-
self.switcher_loc_table = [1, 1, 1, 1, 1]
|
| 97 |
-
|
| 98 |
-
#Initialize variables
|
| 99 |
-
self.index_to_cspr = {}
|
| 100 |
-
self.index_to_db = {}
|
| 101 |
-
self.name_to_db = {}
|
| 102 |
-
self.cspr_files = []
|
| 103 |
-
self.seeds = []
|
| 104 |
-
|
| 105 |
-
self.loading_window = loading_window()
|
| 106 |
-
|
| 107 |
-
self.first_show = True
|
| 108 |
-
scale_ui(self, base_width=1920, base_height=1080, font_size=12, header_font_size=30)
|
| 109 |
-
|
| 110 |
-
except Exception as e:
|
| 111 |
-
show_error("Error initializing population analysis.", e)
|
| 112 |
-
|
| 113 |
-
#export shared seed table to csv function
|
| 114 |
-
def export_tool(self):
|
| 115 |
-
try:
|
| 116 |
-
select_items = self.table2.selectedItems()
|
| 117 |
-
|
| 118 |
-
if len(select_items) <= 0:
|
| 119 |
-
show_message(
|
| 120 |
-
fontSize=12,
|
| 121 |
-
icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 122 |
-
title="Nothing Selected",
|
| 123 |
-
message="No targets were highlighted. Please highlight the targets you want to be exported to a CSV File!"
|
| 124 |
-
)
|
| 125 |
-
return
|
| 126 |
-
|
| 127 |
-
GlobalSettings.mainWindow.export_tool_window.launch(select_items,"pa")
|
| 128 |
-
except Exception as e:
|
| 129 |
-
show_error("Error in export_tool() in population analysis.", e)
|
| 130 |
-
|
| 131 |
-
# this function calls the popParser function and fills all the tables
|
| 132 |
-
def pre_analyze(self):
|
| 133 |
-
try:
|
| 134 |
-
#clear saved filenames
|
| 135 |
-
self.cspr_files = []
|
| 136 |
-
self.db_files = []
|
| 137 |
-
self.rows = []
|
| 138 |
-
|
| 139 |
-
#get selected indexes
|
| 140 |
-
selected_indexes = self.org_Table.selectionModel().selectedRows()
|
| 141 |
-
|
| 142 |
-
if len(selected_indexes) == 0:
|
| 143 |
-
show_message(
|
| 144 |
-
fontSize=12,
|
| 145 |
-
icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 146 |
-
title="Error",
|
| 147 |
-
message="Please select CSPR file(s) for analysis."
|
| 148 |
-
)
|
| 149 |
-
return
|
| 150 |
-
|
| 151 |
-
#get cspr and db filenames
|
| 152 |
-
for index in sorted(selected_indexes):
|
| 153 |
-
self.rows.append(index.row() + 1)
|
| 154 |
-
self.cspr_files.append(self.index_to_cspr[index.row()])
|
| 155 |
-
self.db_files.append(self.index_to_db[index.row()])
|
| 156 |
-
|
| 157 |
-
self.get_org_names()
|
| 158 |
-
self.fill_data()
|
| 159 |
-
except Exception as e:
|
| 160 |
-
show_error("Error in pre_analyze() in population analysis.", e)
|
| 161 |
-
|
| 162 |
-
def launch(self):
|
| 163 |
-
try:
|
| 164 |
-
self.get_data()
|
| 165 |
-
except Exception as e:
|
| 166 |
-
show_error("Error in launch() in population analysis.", e)
|
| 167 |
-
|
| 168 |
-
#responsible for calling all loading/analysis functions for loading the shared seeds table and generating the heatmap based on selected organisms
|
| 169 |
-
def get_data(self):
|
| 170 |
-
try:
|
| 171 |
-
self.fillEndo()
|
| 172 |
-
except Exception as e:
|
| 173 |
-
show_error("Error in get_data() in population analysis.", e)
|
| 174 |
-
|
| 175 |
-
# this function opens CASPERinfo and builds the dropdown menu of selectable endonucleases
|
| 176 |
-
def fillEndo(self):
|
| 177 |
-
try:
|
| 178 |
-
try:
|
| 179 |
-
self.endoBox.currentIndexChanged.disconnect()
|
| 180 |
-
except:
|
| 181 |
-
pass
|
| 182 |
-
|
| 183 |
-
self.Endos = {}
|
| 184 |
-
self.endoBox.clear()
|
| 185 |
-
f = open(GlobalSettings.appdir + 'CASPERinfo')
|
| 186 |
-
while True:
|
| 187 |
-
line = f.readline()
|
| 188 |
-
if line.startswith('ENDONUCLEASES'):
|
| 189 |
-
while True:
|
| 190 |
-
line = f.readline()
|
| 191 |
-
if (line[0] == "-"):
|
| 192 |
-
break
|
| 193 |
-
line_tokened = line.split(";")
|
| 194 |
-
endo = line_tokened[0]
|
| 195 |
-
# Checking to see if there is more than one pam sequence in the list
|
| 196 |
-
if line_tokened[1].find(",") != -1:
|
| 197 |
-
p_pam = line_tokened[1].split(",")[0]
|
| 198 |
-
else:
|
| 199 |
-
p_pam = line_tokened[1]
|
| 200 |
-
default_five_length = line_tokened[2]
|
| 201 |
-
default_seed_length = line_tokened[3]
|
| 202 |
-
default_three_length = line_tokened[4]
|
| 203 |
-
self.Endos[endo + " PAM: " + p_pam] = (endo, p_pam, default_five_length, default_seed_length, default_three_length)
|
| 204 |
-
|
| 205 |
-
break
|
| 206 |
-
f.close()
|
| 207 |
-
self.endoBox.addItems(self.Endos.keys())
|
| 208 |
-
self.endoBox.currentIndexChanged.connect(self.change_endo)
|
| 209 |
-
self.change_endo()
|
| 210 |
-
except Exception as e:
|
| 211 |
-
show_error("Error in fillEndo() in population analysis.", e)
|
| 212 |
-
|
| 213 |
-
#event handler for updating the organism options based on the endo selected
|
| 214 |
-
def change_endo(self):
|
| 215 |
-
try:
|
| 216 |
-
self.org_Table.clearContents()
|
| 217 |
-
self.org_Table.setRowCount(0)
|
| 218 |
-
onlyfiles = [f for f in os.listdir(GlobalSettings.CSPR_DB) if os.path.isfile(os.path.join(GlobalSettings.CSPR_DB, f))]
|
| 219 |
-
index = 0
|
| 220 |
-
for file in onlyfiles:
|
| 221 |
-
if file.find('.cspr') != -1:
|
| 222 |
-
endo = file[file.rfind('_') + 1:file.find('.cspr')]
|
| 223 |
-
if endo == self.Endos[self.endoBox.currentText()][0]:
|
| 224 |
-
|
| 225 |
-
# increase row count
|
| 226 |
-
self.org_Table.setRowCount(index + 1)
|
| 227 |
-
|
| 228 |
-
# open .cspr file and get genome name
|
| 229 |
-
f = open(file, 'r')
|
| 230 |
-
line = f.readline()
|
| 231 |
-
f.close()
|
| 232 |
-
line = str(line)
|
| 233 |
-
line = line.split(":")[-1].strip()
|
| 234 |
-
orgName = line
|
| 235 |
-
# add genome name to table
|
| 236 |
-
tabWidget = QtWidgets.QTableWidgetItem(orgName)
|
| 237 |
-
tabWidget.setTextAlignment(QtCore.Qt.AlignVCenter)
|
| 238 |
-
self.org_Table.setItem(index, 0, tabWidget)
|
| 239 |
-
|
| 240 |
-
# store cspr and db file information for later
|
| 241 |
-
self.index_to_cspr[index] = file
|
| 242 |
-
db_file = file.replace(".cspr", "")
|
| 243 |
-
db_file += "_repeats.db"
|
| 244 |
-
self.index_to_db[index] = db_file
|
| 245 |
-
|
| 246 |
-
# incrase row index
|
| 247 |
-
index += 1
|
| 248 |
-
|
| 249 |
-
if index == 0:
|
| 250 |
-
self.org_Table.clearContents()
|
| 251 |
-
self.org_Table.setRowCount(0)
|
| 252 |
-
|
| 253 |
-
self.org_Table.resizeColumnsToContents()
|
| 254 |
-
except Exception as e:
|
| 255 |
-
show_error("Error in change_endo() in population analysis.", e)
|
| 256 |
-
|
| 257 |
-
#fills shared seed table with data from analysis
|
| 258 |
-
def fill_data(self):
|
| 259 |
-
try:
|
| 260 |
-
#update progress bar
|
| 261 |
-
self.loading_window.loading_bar.setValue(5)
|
| 262 |
-
center_ui(self.loading_window)
|
| 263 |
-
self.loading_window.show()
|
| 264 |
-
QtCore.QCoreApplication.processEvents()
|
| 265 |
-
|
| 266 |
-
#prep table
|
| 267 |
-
self.total_org_number = len(self.cspr_files)
|
| 268 |
-
self.table2.setRowCount(0)
|
| 269 |
-
self.loading_window.loading_bar.setValue(10)
|
| 270 |
-
index = 0
|
| 271 |
-
|
| 272 |
-
self.seeds = self.get_shared_seeds(self.db_files, True)
|
| 273 |
-
|
| 274 |
-
try:
|
| 275 |
-
os.remove(GlobalSettings.appdir + "temp_join.db")
|
| 276 |
-
except:
|
| 277 |
-
pass
|
| 278 |
-
|
| 279 |
-
no_seeds = False
|
| 280 |
-
|
| 281 |
-
if(len(self.seeds) == 0):
|
| 282 |
-
no_seeds = True
|
| 283 |
-
|
| 284 |
-
#QtCore.QCoreApplication.processEvents()
|
| 285 |
-
|
| 286 |
-
#retrieve data on shared seeds
|
| 287 |
-
if no_seeds == False:
|
| 288 |
-
increase_val = float(15 / len(self.seeds))
|
| 289 |
-
running_val = self.loading_window.loading_bar.value()
|
| 290 |
-
self.loading_window.info_label.setText("Parsing Seed Data")
|
| 291 |
-
self.counts = []
|
| 292 |
-
for seed in self.seeds:
|
| 293 |
-
# increase row count
|
| 294 |
-
self.table2.setRowCount(index + 1)
|
| 295 |
-
|
| 296 |
-
# push seed to table
|
| 297 |
-
table_seed = QtWidgets.QTableWidgetItem()
|
| 298 |
-
table_seed.setData(QtCore.Qt.EditRole, seed)
|
| 299 |
-
table_seed.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
|
| 300 |
-
self.table2.setItem(index, 0, table_seed)
|
| 301 |
-
|
| 302 |
-
total_count = 0
|
| 303 |
-
org_count = 0
|
| 304 |
-
threes = []
|
| 305 |
-
fives = []
|
| 306 |
-
scores = []
|
| 307 |
-
pams = []
|
| 308 |
-
locs = []
|
| 309 |
-
|
| 310 |
-
for db_file in self.db_files:
|
| 311 |
-
conn = sqlite3.connect(db_file)
|
| 312 |
-
c = conn.cursor()
|
| 313 |
-
data = c.execute("SELECT count, three, five, pam, score, location FROM repeats WHERE seed = ? ",(seed,)).fetchone()
|
| 314 |
-
if data != None:
|
| 315 |
-
data = list(data)
|
| 316 |
-
print(data)
|
| 317 |
-
org_count += 1
|
| 318 |
-
total_count += int(data[0])
|
| 319 |
-
threes += data[1].split(",")
|
| 320 |
-
fives += data[2].split(",")
|
| 321 |
-
pams += data[3].split(",")
|
| 322 |
-
scores += data[4].split(",")
|
| 323 |
-
print(scores)
|
| 324 |
-
locs += data[5].split(",")
|
| 325 |
-
|
| 326 |
-
self.counts.append(total_count)
|
| 327 |
-
|
| 328 |
-
if len(threes) < len(fives):
|
| 329 |
-
for i in range(len(fives) - len(threes)):
|
| 330 |
-
threes.append('')
|
| 331 |
-
|
| 332 |
-
elif len(fives) < len(threes):
|
| 333 |
-
for i in range(len(threes) - len(fives)):
|
| 334 |
-
fives.append('')
|
| 335 |
-
|
| 336 |
-
majority_index = 0
|
| 337 |
-
three_prime, five_prime, both_prime = False, False, False
|
| 338 |
-
if threes[0] == '':
|
| 339 |
-
majority = max(set(fives), key=fives.count)
|
| 340 |
-
majority_index = fives.index(majority)
|
| 341 |
-
five_prime = True
|
| 342 |
-
elif fives[0] == '':
|
| 343 |
-
majority = max(set(threes), key=threes.count)
|
| 344 |
-
majority_index = threes.index(majority)
|
| 345 |
-
three_prime = True
|
| 346 |
-
else:
|
| 347 |
-
#account for both 3 and 5 present
|
| 348 |
-
threes_and_fives = []
|
| 349 |
-
for i in range(len(threes)):
|
| 350 |
-
threes_and_fives.append(threes[i] + fives[i])
|
| 351 |
-
majority = max(set(threes_and_fives), key=threes_and_fives.count)
|
| 352 |
-
majority_index = threes_and_fives.index(majority)
|
| 353 |
-
both_prime = True
|
| 354 |
-
|
| 355 |
-
# push percent coverage
|
| 356 |
-
perc_cov = QtWidgets.QTableWidgetItem()
|
| 357 |
-
coverage = (org_count / len(self.db_files)) * 100
|
| 358 |
-
coverage = float("%.2f" % coverage)
|
| 359 |
-
perc_cov.setData(QtCore.Qt.EditRole, coverage)
|
| 360 |
-
perc_cov.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
|
| 361 |
-
self.table2.setItem(index, 1, perc_cov)
|
| 362 |
-
|
| 363 |
-
# push total count
|
| 364 |
-
table_count = QtWidgets.QTableWidgetItem()
|
| 365 |
-
table_count.setData(QtCore.Qt.EditRole, total_count)
|
| 366 |
-
table_count.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
|
| 367 |
-
self.table2.setItem(index, 2, table_count)
|
| 368 |
-
|
| 369 |
-
# push avg repeat
|
| 370 |
-
avg_rep = QtWidgets.QTableWidgetItem()
|
| 371 |
-
avg_rep_per_scaff = total_count / org_count
|
| 372 |
-
avg_rep_per_scaff = float("%.2f" % avg_rep_per_scaff)
|
| 373 |
-
avg_rep.setData(QtCore.Qt.EditRole, avg_rep_per_scaff)
|
| 374 |
-
avg_rep.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
|
| 375 |
-
self.table2.setItem(index, 3, avg_rep)
|
| 376 |
-
|
| 377 |
-
# push seq
|
| 378 |
-
seq = QtWidgets.QTableWidgetItem()
|
| 379 |
-
seq.setData(QtCore.Qt.EditRole, fives[majority_index] + seed + threes[majority_index])
|
| 380 |
-
seq.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
|
| 381 |
-
self.table2.setItem(index, 4, seq)
|
| 382 |
-
|
| 383 |
-
# push percent consensus
|
| 384 |
-
perc_cons = QtWidgets.QTableWidgetItem()
|
| 385 |
-
percent_consensus = 0
|
| 386 |
-
if five_prime == True:
|
| 387 |
-
percent_consensus = (fives.count(fives[majority_index]) / len(fives)) * 100
|
| 388 |
-
elif three_prime == True:
|
| 389 |
-
percent_consensus = (threes.count(threes[majority_index]) / len(threes)) * 100
|
| 390 |
-
elif both_prime:
|
| 391 |
-
percent_consensus = (threes_and_fives.count(threes_and_fives[majority_index]) / len(threes_and_fives)) * 100
|
| 392 |
-
|
| 393 |
-
percent_consensus = float("%.2f" % percent_consensus)
|
| 394 |
-
perc_cons.setData(QtCore.Qt.EditRole, percent_consensus)
|
| 395 |
-
perc_cons.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
|
| 396 |
-
self.table2.setItem(index, 5, perc_cons)
|
| 397 |
-
|
| 398 |
-
# push score
|
| 399 |
-
score = QtWidgets.QTableWidgetItem()
|
| 400 |
-
score.setData(QtCore.Qt.EditRole, scores[majority_index])
|
| 401 |
-
score.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
|
| 402 |
-
self.table2.setItem(index, 6, score)
|
| 403 |
-
|
| 404 |
-
# push PAM
|
| 405 |
-
pam = QtWidgets.QTableWidgetItem()
|
| 406 |
-
pam.setData(QtCore.Qt.EditRole, pams[majority_index])
|
| 407 |
-
pam.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
|
| 408 |
-
self.table2.setItem(index, 7, pam)
|
| 409 |
-
|
| 410 |
-
# push strand
|
| 411 |
-
strand_val = ""
|
| 412 |
-
if int(locs[majority_index]) < 0:
|
| 413 |
-
strand_val = "-"
|
| 414 |
-
else:
|
| 415 |
-
strand_val = "+"
|
| 416 |
-
|
| 417 |
-
strand = QtWidgets.QTableWidgetItem()
|
| 418 |
-
strand.setData(QtCore.Qt.EditRole, strand_val)
|
| 419 |
-
strand.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
|
| 420 |
-
self.table2.setItem(index, 8, strand)
|
| 421 |
-
|
| 422 |
-
index += 1
|
| 423 |
-
running_val += increase_val
|
| 424 |
-
self.loading_window.loading_bar.setValue(int(running_val))
|
| 425 |
-
|
| 426 |
-
self.table2.resizeColumnsToContents()
|
| 427 |
-
self.loading_window.loading_bar.setValue(25)
|
| 428 |
-
if len(self.db_files) > 1:
|
| 429 |
-
self.plot_3D_graph()
|
| 430 |
-
else:
|
| 431 |
-
self.colormap_canvas.figure.set_visible(False)
|
| 432 |
-
|
| 433 |
-
self.loading_window.loading_bar.setValue(100)
|
| 434 |
-
self.loading_window.hide()
|
| 435 |
-
QtCore.QCoreApplication.processEvents()
|
| 436 |
-
except Exception as e:
|
| 437 |
-
show_error("Error in fill_data() in population analysis.", e)
|
| 438 |
-
|
| 439 |
-
#function to allow user to search for a specific seed amongst the organisms analyzed
|
| 440 |
-
def custom_seed_search(self):
|
| 441 |
-
try:
|
| 442 |
-
seeds = str(self.seed_input.text())
|
| 443 |
-
seeds = seeds.replace(" ","")
|
| 444 |
-
seeds = seeds.upper()
|
| 445 |
-
seeds = seeds.split(",")
|
| 446 |
-
|
| 447 |
-
if len(seeds) == 1:
|
| 448 |
-
if seeds[0] == "":
|
| 449 |
-
self.pre_analyze()
|
| 450 |
-
return
|
| 451 |
-
|
| 452 |
-
# update progress bar
|
| 453 |
-
self.loading_window.loading_bar.setValue(5)
|
| 454 |
-
self.loading_window.show()
|
| 455 |
-
QtCore.QCoreApplication.processEvents()
|
| 456 |
-
|
| 457 |
-
# prep table
|
| 458 |
-
self.total_org_number = len(self.cspr_files)
|
| 459 |
-
self.loading_window.loading_bar.setValue(10)
|
| 460 |
-
index = 0
|
| 461 |
-
|
| 462 |
-
if (len(seeds) == 0):
|
| 463 |
-
self.loading_window.hide()
|
| 464 |
-
return
|
| 465 |
-
|
| 466 |
-
if len(self.seeds) == 0:
|
| 467 |
-
self.loading_window.hide()
|
| 468 |
-
show_message(
|
| 469 |
-
fontSize=12,
|
| 470 |
-
icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 471 |
-
title="Error",
|
| 472 |
-
message="No analysis has been run to be able to search for a specific seed."
|
| 473 |
-
)
|
| 474 |
-
return
|
| 475 |
-
|
| 476 |
-
increase_val = float(15 / len(self.seeds))
|
| 477 |
-
running_val = self.loading_window.loading_bar.value()
|
| 478 |
-
self.loading_window.info_label.setText("Parsing Seed Data")
|
| 479 |
-
# QtCore.QCoreApplication.processEvents()
|
| 480 |
-
|
| 481 |
-
self.table2.setRowCount(0)
|
| 482 |
-
|
| 483 |
-
# retrieve data on shared seeds
|
| 484 |
-
self.counts = []
|
| 485 |
-
for seed in seeds:
|
| 486 |
-
total_count = 0
|
| 487 |
-
org_count = 0
|
| 488 |
-
threes = []
|
| 489 |
-
fives = []
|
| 490 |
-
scores = []
|
| 491 |
-
pams = []
|
| 492 |
-
locs = []
|
| 493 |
-
none_data = True
|
| 494 |
-
for db_file in self.db_files:
|
| 495 |
-
conn = sqlite3.connect(db_file)
|
| 496 |
-
c = conn.cursor()
|
| 497 |
-
data = c.execute("SELECT count, three, five, pam, score, location FROM repeats WHERE seed = ?",
|
| 498 |
-
(seed,)).fetchone()
|
| 499 |
-
if data != None:
|
| 500 |
-
none_data = False
|
| 501 |
-
data = list(data)
|
| 502 |
-
org_count += 1
|
| 503 |
-
total_count += int(data[0])
|
| 504 |
-
threes += data[1].split(",")
|
| 505 |
-
fives += data[2].split(",")
|
| 506 |
-
pams += data[3].split(",")
|
| 507 |
-
scores += data[4].split(",")
|
| 508 |
-
locs += data[5].split(",")
|
| 509 |
-
|
| 510 |
-
if none_data == True:
|
| 511 |
-
self.loading_window.hide()
|
| 512 |
-
show_message(
|
| 513 |
-
fontSize=12,
|
| 514 |
-
icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 515 |
-
title="Seed Error",
|
| 516 |
-
message=seed + " : No such seed exists in the repeats section of any organism selected."
|
| 517 |
-
)
|
| 518 |
-
return
|
| 519 |
-
|
| 520 |
-
else:
|
| 521 |
-
|
| 522 |
-
# increase row count
|
| 523 |
-
self.table2.setRowCount(index + 1)
|
| 524 |
-
|
| 525 |
-
# push seed to table
|
| 526 |
-
table_seed = QtWidgets.QTableWidgetItem()
|
| 527 |
-
table_seed.setData(QtCore.Qt.EditRole, seed)
|
| 528 |
-
table_seed.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
|
| 529 |
-
self.table2.setItem(index, 0, table_seed)
|
| 530 |
-
|
| 531 |
-
self.counts.append(total_count)
|
| 532 |
-
|
| 533 |
-
if len(threes) < len(fives):
|
| 534 |
-
for i in range(len(fives) - len(threes)):
|
| 535 |
-
threes.append('')
|
| 536 |
-
elif len(fives) < len(threes):
|
| 537 |
-
for i in range(len(threes) - len(fives)):
|
| 538 |
-
fives.append('')
|
| 539 |
-
|
| 540 |
-
majority_index = 0
|
| 541 |
-
three_prime, five_prime, both_prime = False, False, False
|
| 542 |
-
if threes[0] == '':
|
| 543 |
-
majority = max(set(fives), key=fives.count)
|
| 544 |
-
majority_index = fives.index(majority)
|
| 545 |
-
five_prime = True
|
| 546 |
-
elif fives[0] == '':
|
| 547 |
-
majority = max(set(threes), key=threes.count)
|
| 548 |
-
majority_index = threes.index(majority)
|
| 549 |
-
three_prime = True
|
| 550 |
-
else:
|
| 551 |
-
# account for both 3 and 5 present
|
| 552 |
-
threes_and_fives = []
|
| 553 |
-
for i in range(len(threes)):
|
| 554 |
-
threes_and_fives.append(threes[i] + fives[i])
|
| 555 |
-
majority = max(set(threes_and_fives), key=threes_and_fives.count)
|
| 556 |
-
majority_index = threes_and_fives.index(majority)
|
| 557 |
-
both_prime = True
|
| 558 |
-
|
| 559 |
-
# push percent coverage
|
| 560 |
-
perc_cov = QtWidgets.QTableWidgetItem()
|
| 561 |
-
coverage = (org_count / len(self.db_files)) * 100
|
| 562 |
-
coverage = float("%.2f" % coverage)
|
| 563 |
-
perc_cov.setData(QtCore.Qt.EditRole, coverage)
|
| 564 |
-
perc_cov.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
|
| 565 |
-
self.table2.setItem(index, 1, perc_cov)
|
| 566 |
-
|
| 567 |
-
# push total count
|
| 568 |
-
table_count = QtWidgets.QTableWidgetItem()
|
| 569 |
-
table_count.setData(QtCore.Qt.EditRole, total_count)
|
| 570 |
-
table_count.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
|
| 571 |
-
self.table2.setItem(index, 2, table_count)
|
| 572 |
-
|
| 573 |
-
# push avg repeat
|
| 574 |
-
avg_rep = QtWidgets.QTableWidgetItem()
|
| 575 |
-
avg_rep_per_scaff = total_count / org_count
|
| 576 |
-
avg_rep_per_scaff = float("%.2f" % avg_rep_per_scaff)
|
| 577 |
-
avg_rep.setData(QtCore.Qt.EditRole, avg_rep_per_scaff)
|
| 578 |
-
avg_rep.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
|
| 579 |
-
self.table2.setItem(index, 3, avg_rep)
|
| 580 |
-
|
| 581 |
-
# push seq
|
| 582 |
-
seq = QtWidgets.QTableWidgetItem()
|
| 583 |
-
seq.setData(QtCore.Qt.EditRole, fives[majority_index] + seed + threes[majority_index])
|
| 584 |
-
seq.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
|
| 585 |
-
self.table2.setItem(index, 4, seq)
|
| 586 |
-
|
| 587 |
-
# push percent consensus
|
| 588 |
-
perc_cons = QtWidgets.QTableWidgetItem()
|
| 589 |
-
percent_consensus = 0
|
| 590 |
-
if five_prime == True:
|
| 591 |
-
percent_consensus = (fives.count(fives[majority_index]) / len(fives)) * 100
|
| 592 |
-
elif three_prime == True:
|
| 593 |
-
percent_consensus = (threes.count(threes[majority_index]) / len(threes)) * 100
|
| 594 |
-
elif both_prime:
|
| 595 |
-
percent_consensus = (threes_and_fives.count(threes_and_fives[majority_index]) / len(threes_and_fives)) * 100
|
| 596 |
-
percent_consensus = float("%.2f" % percent_consensus)
|
| 597 |
-
perc_cons.setData(QtCore.Qt.EditRole, percent_consensus)
|
| 598 |
-
perc_cons.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
|
| 599 |
-
self.table2.setItem(index, 5, perc_cons)
|
| 600 |
-
|
| 601 |
-
# push score
|
| 602 |
-
score = QtWidgets.QTableWidgetItem()
|
| 603 |
-
score.setData(QtCore.Qt.EditRole, scores[majority_index])
|
| 604 |
-
score.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
|
| 605 |
-
self.table2.setItem(index, 6, score)
|
| 606 |
-
|
| 607 |
-
# push PAM
|
| 608 |
-
pam = QtWidgets.QTableWidgetItem()
|
| 609 |
-
pam.setData(QtCore.Qt.EditRole, pams[majority_index])
|
| 610 |
-
pam.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
|
| 611 |
-
self.table2.setItem(index, 7, pam)
|
| 612 |
-
|
| 613 |
-
# push strand
|
| 614 |
-
strand_val = ""
|
| 615 |
-
if int(locs[majority_index]) < 0:
|
| 616 |
-
strand_val = "-"
|
| 617 |
-
else:
|
| 618 |
-
strand_val = "+"
|
| 619 |
-
|
| 620 |
-
strand = QtWidgets.QTableWidgetItem()
|
| 621 |
-
strand.setData(QtCore.Qt.EditRole, strand_val)
|
| 622 |
-
strand.setTextAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
|
| 623 |
-
self.table2.setItem(index, 8, strand)
|
| 624 |
-
|
| 625 |
-
index += 1
|
| 626 |
-
running_val += increase_val
|
| 627 |
-
self.loading_window.loading_bar.setValue(int(running_val))
|
| 628 |
-
|
| 629 |
-
self.table2.resizeColumnsToContents()
|
| 630 |
-
self.loading_window.hide()
|
| 631 |
-
QtCore.QCoreApplication.processEvents()
|
| 632 |
-
except Exception as e:
|
| 633 |
-
show_error("Error in custom_seed_search() in population analysis.", e)
|
| 634 |
-
|
| 635 |
-
#db_files is an array of database files for the organisms that will be looked at for shared seeds
|
| 636 |
-
def get_shared_seeds(self, db_files, limit=False):
|
| 637 |
-
try:
|
| 638 |
-
aliases = []
|
| 639 |
-
|
| 640 |
-
#get db attachment aliases
|
| 641 |
-
for i in range(1, len(db_files) + 1):
|
| 642 |
-
aliases.append("main" + str(i))
|
| 643 |
-
|
| 644 |
-
#memory connections for inner join on db files to hold what seeds are shared
|
| 645 |
-
new_conn = sqlite3.connect(GlobalSettings.appdir + "temp_join.db")
|
| 646 |
-
new_c = new_conn.cursor()
|
| 647 |
-
new_c.execute("PRAGMA synchronous = OFF;")
|
| 648 |
-
new_c.execute("PRAGMA journal_mode = OFF;")
|
| 649 |
-
new_c.execute("PRAGMA locking_mode = EXCLUSIVE;")
|
| 650 |
-
new_c.execute("DROP TABLE IF EXISTS repeats;")
|
| 651 |
-
new_c.execute("VACUUM;")
|
| 652 |
-
new_c.execute("DROP TABLE IF EXISTS join_results;")
|
| 653 |
-
new_c.execute("CREATE table join_results (seed TEXT PRIMARY KEY);")
|
| 654 |
-
|
| 655 |
-
#attach each db file with an alias
|
| 656 |
-
for i in range(len(db_files)):
|
| 657 |
-
new_c.execute("ATTACH DATABASE '" + db_files[i] + "' AS " + aliases[i] + ";")
|
| 658 |
-
|
| 659 |
-
# start transaction
|
| 660 |
-
new_c.execute("BEGIN TRANSACTION;")
|
| 661 |
-
|
| 662 |
-
sql_inner_join = "INSERT into main.join_results select main1.repeats.seed from main1.repeats "
|
| 663 |
-
|
| 664 |
-
for i in range(len(aliases[:-1])):
|
| 665 |
-
sql_inner_join += "inner join " + aliases[i + 1] + ".repeats on "
|
| 666 |
-
sql_inner_join += aliases[i] + ".repeats.seed = " + aliases[i + 1] + ".repeats.seed "
|
| 667 |
-
|
| 668 |
-
#execute inner join
|
| 669 |
-
new_c.execute(sql_inner_join)
|
| 670 |
-
|
| 671 |
-
#get shared data
|
| 672 |
-
if limit == False:
|
| 673 |
-
shared_seeds = new_c.execute("select count(*) from join_results").fetchall()
|
| 674 |
-
return shared_seeds
|
| 675 |
-
else:
|
| 676 |
-
shared_seeds = new_c.execute("select * from join_results limit 0,1000").fetchall()
|
| 677 |
-
|
| 678 |
-
#end transaction
|
| 679 |
-
new_c.execute("END TRANSACTION;")
|
| 680 |
-
|
| 681 |
-
#close memory db
|
| 682 |
-
new_c.close()
|
| 683 |
-
new_conn.close()
|
| 684 |
-
|
| 685 |
-
#parse shared seeds into self.seeds
|
| 686 |
-
seeds = []
|
| 687 |
-
for tup in shared_seeds:
|
| 688 |
-
seeds.append(tup[0])
|
| 689 |
-
|
| 690 |
-
return seeds
|
| 691 |
-
|
| 692 |
-
except Exception as e:
|
| 693 |
-
show_error("Error in get_shared_seeds() in population analysis.", e)
|
| 694 |
-
|
| 695 |
-
#get the names of organism in current directory
|
| 696 |
-
def get_org_names(self):
|
| 697 |
-
try:
|
| 698 |
-
self.org_names = {}
|
| 699 |
-
for file in self.cspr_files:
|
| 700 |
-
with open(file, "r") as f:
|
| 701 |
-
line = f.readline()
|
| 702 |
-
buf = str(line)
|
| 703 |
-
buf = buf.strip()
|
| 704 |
-
org_name = buf.replace("GENOME: ", "")
|
| 705 |
-
|
| 706 |
-
line = f.readline()
|
| 707 |
-
buf = str(line)
|
| 708 |
-
kstats = buf.replace("KARYSTATS: ", "")
|
| 709 |
-
kstats = kstats.split(",")
|
| 710 |
-
self.org_names[org_name] = len(kstats) - 1
|
| 711 |
-
except Exception as e:
|
| 712 |
-
show_error("Error in get_org_names() in population analysis.", e)
|
| 713 |
-
|
| 714 |
-
#plot the heatmap graph
|
| 715 |
-
def plot_3D_graph(self):
|
| 716 |
-
try:
|
| 717 |
-
for i in reversed(range(self.colormap_layout.count())): ### Clear out old widges in layout
|
| 718 |
-
self.colormap_layout.itemAt(i).widget().setParent(None)
|
| 719 |
-
|
| 720 |
-
self.colormap_canvas = MplCanvas(self, width=5, height=3, dpi=self.dpi) ###Initialize new Canvas
|
| 721 |
-
self.colormap_layout.addWidget(self.colormap_canvas) ### Add canvas to colormap layout
|
| 722 |
-
self.colormap_figure.setLayout(self.colormap_layout) ### Add colormap layout to colormap plot widget
|
| 723 |
-
|
| 724 |
-
# self.colormap_canvas.axes.clear()
|
| 725 |
-
# try:
|
| 726 |
-
# self.colormap_canvas.cbar.remove()
|
| 727 |
-
# except:
|
| 728 |
-
# None
|
| 729 |
-
# self.colormap_canvas.figure.set_visible(True)
|
| 730 |
-
|
| 731 |
-
rows, cols = (self.total_org_number, self.total_org_number)
|
| 732 |
-
arr = [[0 for i in range(cols)] for j in range(rows)]
|
| 733 |
-
|
| 734 |
-
self.names = list(self.org_names.keys())
|
| 735 |
-
|
| 736 |
-
for pair in list(itertools.combinations(self.db_files, 2)):
|
| 737 |
-
shared_seeds = list(self.get_shared_seeds(list(pair), False)[0])[0]
|
| 738 |
-
|
| 739 |
-
arr[self.db_files.index(pair[0])][self.db_files.index(pair[1])] += int(shared_seeds)
|
| 740 |
-
arr[self.db_files.index(pair[1])][self.db_files.index(pair[0])] += int(shared_seeds)
|
| 741 |
-
|
| 742 |
-
for i in range(len(arr)):
|
| 743 |
-
conn = sqlite3.connect(self.db_files[i])
|
| 744 |
-
c = conn.cursor()
|
| 745 |
-
arr[i][i] = int(list(c.execute("select count(*) from repeats;").fetchall()[0])[0])
|
| 746 |
-
c.close()
|
| 747 |
-
conn.close()
|
| 748 |
-
|
| 749 |
-
labels = copy.deepcopy(arr)
|
| 750 |
-
|
| 751 |
-
for i in range(len(arr)):
|
| 752 |
-
arr[i][i] = 0
|
| 753 |
-
|
| 754 |
-
ax = self.colormap_canvas.axes
|
| 755 |
-
im = self.colormap_canvas.axes.imshow(arr, cmap='summer')
|
| 756 |
-
self.colormap_canvas.cbar = self.colormap_canvas.axes.figure.colorbar(im, ax=self.colormap_canvas.axes)
|
| 757 |
-
self.colormap_canvas.cbar.ax.set_ylabel("", rotation=-90, va="bottom",fontsize=8)
|
| 758 |
-
cursor = mplcursors.cursor(im, hover=True)
|
| 759 |
-
@cursor.connect("add")
|
| 760 |
-
def on_add(sel):
|
| 761 |
-
sel.annotation.arrow_patch.set(arrowstyle="simple", fc="white", alpha=.5)
|
| 762 |
-
sel.annotation.set_bbox(None)
|
| 763 |
-
i,j = sel.target.index
|
| 764 |
-
sel.annotation.set_text(labels[i][j])
|
| 765 |
-
|
| 766 |
-
ax.set_xticks(np.arange(len(arr)))
|
| 767 |
-
ax.set_yticks(np.arange(len(arr)))
|
| 768 |
-
|
| 769 |
-
def plotCellGrid(data, ax=None, **kwargs):
|
| 770 |
-
for x in range(data[0]):
|
| 771 |
-
for y in range(data[1]):
|
| 772 |
-
rect = patches.Rectangle((x - .5, y - .5), 1, 1, fill=False, **kwargs)
|
| 773 |
-
ax.add_patch(rect)
|
| 774 |
-
|
| 775 |
-
#get labels based on org table rows
|
| 776 |
-
ax.set_xticklabels(self.rows)
|
| 777 |
-
ax.set_yticklabels(self.rows)
|
| 778 |
-
ax.set_xlabel("Organism", fontsize = 10)
|
| 779 |
-
ax.set_ylabel("Organism", fontsize = 10)
|
| 780 |
-
ax.tick_params(axis='both', which='major', labelsize=8)
|
| 781 |
-
plotCellGrid([len(self.rows), len(self.rows)], ax, color="black", linewidth=1)
|
| 782 |
-
|
| 783 |
-
self.colormap_canvas.draw()
|
| 784 |
-
except Exception as e:
|
| 785 |
-
show_error("Error in plot_3D_graph() in population analysis.", e)
|
| 786 |
-
|
| 787 |
-
#find the locations of selected seeds to load into the location table
|
| 788 |
-
def find_locations(self):
|
| 789 |
-
try:
|
| 790 |
-
if len(self.table2.selectedItems()) == 0:
|
| 791 |
-
show_message(
|
| 792 |
-
fontSize=12,
|
| 793 |
-
icon=QtWidgets.QMessageBox.Icon.Critical,
|
| 794 |
-
title="Error",
|
| 795 |
-
message="Please select at least 1 seed to find locations of."
|
| 796 |
-
)
|
| 797 |
-
return
|
| 798 |
-
|
| 799 |
-
#get seeds from selected rows in table
|
| 800 |
-
seeds = []
|
| 801 |
-
for i in self.table2.selectionModel().selectedRows():
|
| 802 |
-
item = self.table2.item(i.row(), 0)
|
| 803 |
-
seeds.append(item.text())
|
| 804 |
-
|
| 805 |
-
index = 0
|
| 806 |
-
org_names = list(self.org_names.keys())
|
| 807 |
-
#loop through each db table looking for the seed, if found, insert data in to locations table
|
| 808 |
-
for db_file in self.db_files:
|
| 809 |
-
conn = sqlite3.connect(db_file)
|
| 810 |
-
c = conn.cursor()
|
| 811 |
-
for seed in seeds:
|
| 812 |
-
data = c.execute("SELECT chromosome, location, five, three FROM repeats WHERE seed = ? ", (seed,)).fetchone()
|
| 813 |
-
#make sure seed was found in table, then parse and store in table
|
| 814 |
-
if data != None:
|
| 815 |
-
data = list(data)
|
| 816 |
-
chroms = data[0].split(',')
|
| 817 |
-
locs = data[1].split(',')
|
| 818 |
-
fives = data[2].split(',')
|
| 819 |
-
threes = data[3].split(',')
|
| 820 |
-
if threes[0] == '':
|
| 821 |
-
threes = []
|
| 822 |
-
if fives[0] == '':
|
| 823 |
-
fives = []
|
| 824 |
-
i = 0
|
| 825 |
-
for chrom in chroms:
|
| 826 |
-
self.loc_finder_table.setRowCount(index + 1)
|
| 827 |
-
seed_table = QtWidgets.QTableWidgetItem()
|
| 828 |
-
sequence_table = QtWidgets.QTableWidgetItem()
|
| 829 |
-
organism_table = QtWidgets.QTableWidgetItem()
|
| 830 |
-
chromsome_table = QtWidgets.QTableWidgetItem()
|
| 831 |
-
location_table = QtWidgets.QTableWidgetItem()
|
| 832 |
-
seed_table.setData(QtCore.Qt.EditRole, seed)
|
| 833 |
-
if len(fives) == 0 and len(threes) != 0:
|
| 834 |
-
sequence_table.setData(QtCore.Qt.EditRole, seed + threes[i])
|
| 835 |
-
elif len(threes) == 0 and len(fives) != 0:
|
| 836 |
-
sequence_table.setData(QtCore.Qt.EditRole, fives[i] + seed)
|
| 837 |
-
else:
|
| 838 |
-
sequence_table.setData(QtCore.Qt.EditRole, fives[i] + seed + threes[i])
|
| 839 |
-
organism_table.setData(QtCore.Qt.EditRole, org_names[self.db_files.index(db_file)])
|
| 840 |
-
chromsome_table.setData(QtCore.Qt.EditRole, chrom)
|
| 841 |
-
location_table.setData(QtCore.Qt.EditRole, abs(int(locs[i])))
|
| 842 |
-
self.loc_finder_table.setItem(index, 0, seed_table)
|
| 843 |
-
self.loc_finder_table.setItem(index, 1, sequence_table)
|
| 844 |
-
self.loc_finder_table.setItem(index, 2, organism_table)
|
| 845 |
-
self.loc_finder_table.setItem(index, 3, chromsome_table)
|
| 846 |
-
self.loc_finder_table.setItem(index, 4, location_table)
|
| 847 |
-
i += 1
|
| 848 |
-
index += 1
|
| 849 |
-
|
| 850 |
-
self.loc_finder_table.resizeColumnsToContents()
|
| 851 |
-
except Exception as e:
|
| 852 |
-
show_error("Error in find_locations() in population analysis.", e)
|
| 853 |
-
|
| 854 |
-
# this function clears the loc_finder_table
|
| 855 |
-
def clear_loc_table(self):
|
| 856 |
-
try:
|
| 857 |
-
self.loc_finder_table.clearContents()
|
| 858 |
-
self.loc_finder_table.setRowCount(0)
|
| 859 |
-
except Exception as e:
|
| 860 |
-
show_error("Error in clear_loc_table() in population analysis.", e)
|
| 861 |
-
|
| 862 |
-
# sorting function for table2 - shared seeds table: IE the table in top-right
|
| 863 |
-
def table2_sorting(self, logicalIndex):
|
| 864 |
-
try:
|
| 865 |
-
self.switcher_table2[logicalIndex] *= -1
|
| 866 |
-
if self.switcher_table2[logicalIndex] == -1:
|
| 867 |
-
self.table2.sortItems(logicalIndex, QtCore.Qt.DescendingOrder)
|
| 868 |
-
else:
|
| 869 |
-
self.table2.sortItems(logicalIndex, QtCore.Qt.AscendingOrder)
|
| 870 |
-
except Exception as e:
|
| 871 |
-
show_error("Error in table2_sorting() in population analysis.", e)
|
| 872 |
-
|
| 873 |
-
# sorting for location table: IE table in bottom right
|
| 874 |
-
def loc_table_sorter(self, logicalIndex):
|
| 875 |
-
try:
|
| 876 |
-
self.switcher_loc_table[logicalIndex] *= -1
|
| 877 |
-
if (self.switcher_loc_table[logicalIndex] == -1):
|
| 878 |
-
self.loc_finder_table.sortItems(logicalIndex, QtCore.Qt.DescendingOrder)
|
| 879 |
-
else:
|
| 880 |
-
self.loc_finder_table.sortItems(logicalIndex, QtCore.Qt.AscendingOrder)
|
| 881 |
-
except Exception as e:
|
| 882 |
-
show_error("Error in loc_table_sorter() in population analysis.", e)
|
| 883 |
-
|
| 884 |
-
#clears the table showcasing shared seeds
|
| 885 |
-
def clear(self):
|
| 886 |
-
try:
|
| 887 |
-
self.table2.setRowCount(0)
|
| 888 |
-
except Exception as e:
|
| 889 |
-
show_error("Error in clear() in population analysis.", e)
|
| 890 |
-
|
| 891 |
-
#return to main function
|
| 892 |
-
def go_back(self):
|
| 893 |
-
try:
|
| 894 |
-
GlobalSettings.mainWindow.show()
|
| 895 |
-
self.hide()
|
| 896 |
-
except Exception as e:
|
| 897 |
-
show_error("Error in go_back() in population analysis.", e)
|
| 898 |
-
|
| 899 |
-
# this function calls the close window class. Allows the user to choose what files they want to keep/delete
|
| 900 |
-
def closeEvent(self, event):
|
| 901 |
-
try:
|
| 902 |
-
GlobalSettings.mainWindow.closeFunction()
|
| 903 |
-
event.accept()
|
| 904 |
-
except Exception as e:
|
| 905 |
-
show_error("Error in closeEvent() in population analysis.", e)
|
| 906 |
-
|
| 907 |
-
#loading window UI class for when data is loading
|
| 908 |
-
class loading_window(QtWidgets.QMainWindow):
|
| 909 |
-
def __init__(self):
|
| 910 |
-
try:
|
| 911 |
-
super(loading_window, self).__init__()
|
| 912 |
-
uic.loadUi(GlobalSettings.appdir + "ui/loading_data_form.ui", self)
|
| 913 |
-
self.loading_bar.setValue(0)
|
| 914 |
-
self.setWindowTitle("Loading Data")
|
| 915 |
-
self.setWindowIcon(Qt.QIcon(GlobalSettings.appdir + "cas9image.ico"))
|
| 916 |
-
scale_ui(self, base_width=1920, base_height=1080, font_size=12, header_font_size=30, custom_scale_width=450, custom_scale_height=125)
|
| 917 |
-
except Exception as e:
|
| 918 |
-
show_error("Error initializing loading_window class in population analysis.", e)
|
| 919 |
-
|
| 920 |
-
#matplotlib canvas class for the heatmap graph
|
| 921 |
-
class MplCanvas(FigureCanvasQTAgg):
|
| 922 |
-
def __init__(self, parent=None, width=400, height=250, dpi=100):
|
| 923 |
-
try:
|
| 924 |
-
fig = Figure(dpi=dpi, tight_layout=True)
|
| 925 |
-
self.axes = fig.add_subplot(111)
|
| 926 |
-
self.axes.clear()
|
| 927 |
-
super(MplCanvas, self).__init__(fig)
|
| 928 |
-
except Exception as e:
|
| 929 |
-
show_error("Error initializing MplCanvas class in population analysis.", e)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,32 +1,59 @@
|
|
| 1 |
import sys
|
| 2 |
import os
|
|
|
|
| 3 |
from PyQt6.QtWidgets import QApplication
|
| 4 |
from PyQt6.QtCore import Qt
|
| 5 |
from models.GlobalSettings import GlobalSettings
|
| 6 |
from utils.ui import show_error
|
| 7 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
def main():
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
-
|
| 15 |
-
|
| 16 |
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
-
|
| 20 |
-
global_settings.apply_theme()
|
| 21 |
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
main_window_controller.show()
|
| 26 |
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
|
|
|
|
|
|
| 30 |
|
| 31 |
if __name__ == '__main__':
|
| 32 |
main()
|
|
|
|
| 1 |
import sys
|
| 2 |
import os
|
| 3 |
+
import platform
|
| 4 |
from PyQt6.QtWidgets import QApplication
|
| 5 |
from PyQt6.QtCore import Qt
|
| 6 |
from models.GlobalSettings import GlobalSettings
|
| 7 |
from utils.ui import show_error
|
| 8 |
+
|
| 9 |
+
def get_app_directory():
|
| 10 |
+
"""Determine the application root directory based on whether we're frozen or not"""
|
| 11 |
+
if hasattr(sys, 'frozen'):
|
| 12 |
+
if platform.system() == 'Darwin': # macOS
|
| 13 |
+
# Get the path to the executable inside the .app bundle
|
| 14 |
+
bundle_dir = os.path.abspath(os.path.dirname(sys.executable))
|
| 15 |
+
# Navigate up to Contents directory and set Resources as app_dir
|
| 16 |
+
return os.path.join(os.path.dirname(os.path.dirname(bundle_dir)), 'Contents', 'Resources')
|
| 17 |
+
else:
|
| 18 |
+
# For other platforms when frozen
|
| 19 |
+
return os.path.dirname(sys.executable)
|
| 20 |
+
else:
|
| 21 |
+
# Development environment - go up one level from src directory
|
| 22 |
+
return os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 23 |
|
| 24 |
def main():
|
| 25 |
+
RESTART_CODE = 1000 # Define restart code constant
|
| 26 |
+
|
| 27 |
+
while True:
|
| 28 |
+
app = QApplication(sys.argv)
|
| 29 |
+
app.setOrganizationName("TrinhLab-UTK")
|
| 30 |
+
app.setApplicationName("CASPER")
|
| 31 |
+
|
| 32 |
+
if hasattr(Qt.ApplicationAttribute, 'AA_UseHighDpiPixmaps'):
|
| 33 |
+
app.setAttribute(Qt.ApplicationAttribute.AA_UseHighDpiPixmaps)
|
| 34 |
|
| 35 |
+
# Get the application directory
|
| 36 |
+
app_dir_path = get_app_directory()
|
| 37 |
|
| 38 |
+
try:
|
| 39 |
+
global_settings = GlobalSettings(app_dir_path)
|
| 40 |
+
|
| 41 |
+
from controllers.MainWindowController import MainWindowController
|
| 42 |
+
main_window_controller = MainWindowController(global_settings)
|
| 43 |
+
global_settings.set_main_window(main_window_controller)
|
| 44 |
+
main_window_controller.show()
|
| 45 |
|
| 46 |
+
exit_code = app.exec()
|
|
|
|
| 47 |
|
| 48 |
+
if exit_code != RESTART_CODE:
|
| 49 |
+
sys.exit(exit_code)
|
| 50 |
+
break
|
|
|
|
| 51 |
|
| 52 |
+
main_window_controller = None
|
| 53 |
+
global_settings = None
|
| 54 |
+
app = None
|
| 55 |
+
except Exception as e:
|
| 56 |
+
show_error(global_settings, "An error occurred during application initialization", e)
|
| 57 |
|
| 58 |
if __name__ == '__main__':
|
| 59 |
main()
|
|
@@ -19,7 +19,14 @@ class AnnotationParser:
|
|
| 19 |
self.index_file = None
|
| 20 |
|
| 21 |
def set_annotation_file(self, file_path):
|
|
|
|
| 22 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
if self.annotation_file_name != file_path:
|
| 24 |
total_start = time.time()
|
| 25 |
|
|
@@ -49,10 +56,9 @@ class AnnotationParser:
|
|
| 49 |
start_time = time.time()
|
| 50 |
self.logger.debug("Creating gene index file...")
|
| 51 |
|
| 52 |
-
# Initialize index structure
|
| 53 |
index_data = {
|
| 54 |
-
'locus_tags': {}, # Only store
|
| 55 |
-
'sequences': {} # Keep sequences for quick access
|
| 56 |
}
|
| 57 |
|
| 58 |
# Process records
|
|
@@ -63,44 +69,49 @@ class AnnotationParser:
|
|
| 63 |
record_count += 1
|
| 64 |
record_start = time.time()
|
| 65 |
|
| 66 |
-
# Store sequence information first
|
| 67 |
-
index_data['sequences'][record.id] = str(record.seq)
|
| 68 |
-
|
| 69 |
# Process features
|
| 70 |
for feature in record.features:
|
| 71 |
if feature.type in ['CDS', 'gene']:
|
| 72 |
feature_count += 1
|
| 73 |
-
feature_info = self._get_feature_info(feature)
|
| 74 |
-
locus_tag = feature_info['feature_id']
|
| 75 |
|
| 76 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
if locus_tag and locus_tag.lower() != "n/a":
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
feature_entry = {
|
| 79 |
-
'record_id': record.id,
|
| 80 |
'feature_type': feature.type,
|
| 81 |
'chromosome': record.id,
|
| 82 |
-
'location':
|
| 83 |
-
'
|
| 84 |
-
'
|
| 85 |
-
|
| 86 |
-
'
|
| 87 |
-
'
|
| 88 |
-
for k, v in feature.qualifiers.items()}
|
| 89 |
}
|
| 90 |
|
| 91 |
-
#
|
| 92 |
-
index_data['locus_tags'][locus_tag
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
|
| 98 |
-
# Save index to file
|
| 99 |
save_start = time.time()
|
| 100 |
with open(self.index_file, 'wb') as f:
|
| 101 |
-
pickle.dump(index_data, f)
|
| 102 |
save_time = time.time() - save_start
|
| 103 |
-
|
| 104 |
total_time = time.time() - start_time
|
| 105 |
|
| 106 |
self._index = index_data
|
|
@@ -127,6 +138,7 @@ class AnnotationParser:
|
|
| 127 |
with open(self.index_file, 'rb') as f:
|
| 128 |
self._index = pickle.load(f)
|
| 129 |
load_time = time.time() - start_time
|
|
|
|
| 130 |
self.logger.debug(f"Index file loaded successfully in {load_time:.2f} seconds")
|
| 131 |
return True
|
| 132 |
|
|
@@ -137,47 +149,54 @@ class AnnotationParser:
|
|
| 137 |
def genbank_search(self, queries):
|
| 138 |
"""Search using the index file for better performance"""
|
| 139 |
try:
|
| 140 |
-
if not self.annotation_file_name:
|
| 141 |
-
|
|
|
|
| 142 |
|
| 143 |
self.logger.debug(f"Searching in annotation file: {self.annotation_file_name}")
|
| 144 |
results_list = []
|
| 145 |
|
| 146 |
# Convert queries to lowercase set for faster lookup
|
| 147 |
queries = {q.lower() for q in queries}
|
| 148 |
-
|
| 149 |
|
| 150 |
# Search through index
|
| 151 |
-
if hasattr(self, '_index'):
|
| 152 |
-
# Search through
|
| 153 |
-
for
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
# Check gene name, locus tag, and description
|
| 155 |
searchable_text = ' '.join([
|
| 156 |
-
feature_entry
|
| 157 |
-
|
| 158 |
-
feature_entry
|
| 159 |
-
# Also search through qualifiers
|
| 160 |
-
*[str(v).lower() for v in feature_entry['qualifiers'].values()]
|
| 161 |
])
|
| 162 |
|
| 163 |
# Check if any query matches
|
| 164 |
if any(query in searchable_text for query in queries):
|
| 165 |
info = {
|
| 166 |
-
'feature_id':
|
| 167 |
-
'feature_name': feature_entry
|
| 168 |
-
'feature_location': feature_entry
|
| 169 |
-
'feature_description': feature_entry
|
| 170 |
}
|
| 171 |
-
results_list.append((feature_entry
|
| 172 |
|
| 173 |
return results_list
|
| 174 |
|
| 175 |
except Exception as e:
|
| 176 |
self.logger.error(f"Error in genbank_search: {str(e)}")
|
|
|
|
| 177 |
raise
|
| 178 |
|
| 179 |
def get_gene_data(self, gene_identifier):
|
| 180 |
-
"""Get gene data using the index
|
| 181 |
try:
|
| 182 |
if not gene_identifier:
|
| 183 |
return None
|
|
@@ -189,27 +208,81 @@ class AnnotationParser:
|
|
| 189 |
# Try exact match first
|
| 190 |
if gene_identifier in self._index['locus_tags']:
|
| 191 |
gene_info = self._index['locus_tags'][gene_identifier]
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
}
|
| 197 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 198 |
# Try case-insensitive match
|
| 199 |
for key, value in self._index['locus_tags'].items():
|
| 200 |
if str(key).lower() == gene_identifier:
|
| 201 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
return {
|
| 203 |
-
'sequence':
|
| 204 |
-
'info':
|
| 205 |
}
|
| 206 |
|
| 207 |
return None
|
| 208 |
-
|
| 209 |
except Exception as e:
|
| 210 |
self.logger.error(f"Error in get_gene_data: {str(e)}")
|
| 211 |
return None
|
| 212 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 213 |
@lru_cache(maxsize=1)
|
| 214 |
def _get_records(self):
|
| 215 |
"""Cache and return all records from the annotation file"""
|
|
|
|
| 19 |
self.index_file = None
|
| 20 |
|
| 21 |
def set_annotation_file(self, file_path):
|
| 22 |
+
"""Set the annotation file and initialize/load index"""
|
| 23 |
try:
|
| 24 |
+
# Don't process if file_path is a directory or empty
|
| 25 |
+
if not file_path or os.path.isdir(file_path):
|
| 26 |
+
self.logger.debug(f"Invalid annotation file path: {file_path}")
|
| 27 |
+
self._index = {'locus_tags': {}} # Initialize empty index
|
| 28 |
+
return
|
| 29 |
+
|
| 30 |
if self.annotation_file_name != file_path:
|
| 31 |
total_start = time.time()
|
| 32 |
|
|
|
|
| 56 |
start_time = time.time()
|
| 57 |
self.logger.debug("Creating gene index file...")
|
| 58 |
|
| 59 |
+
# Initialize optimized index structure - no sequences stored
|
| 60 |
index_data = {
|
| 61 |
+
'locus_tags': {}, # Only store essential data
|
|
|
|
| 62 |
}
|
| 63 |
|
| 64 |
# Process records
|
|
|
|
| 69 |
record_count += 1
|
| 70 |
record_start = time.time()
|
| 71 |
|
|
|
|
|
|
|
|
|
|
| 72 |
# Process features
|
| 73 |
for feature in record.features:
|
| 74 |
if feature.type in ['CDS', 'gene']:
|
| 75 |
feature_count += 1
|
|
|
|
|
|
|
| 76 |
|
| 77 |
+
# Get essential feature info
|
| 78 |
+
locus_tag = None
|
| 79 |
+
if 'locus_tag' in feature.qualifiers:
|
| 80 |
+
locus_tag = feature.qualifiers['locus_tag'][0]
|
| 81 |
+
elif 'gene' in feature.qualifiers:
|
| 82 |
+
locus_tag = feature.qualifiers['gene'][0]
|
| 83 |
+
|
| 84 |
+
# Only process features with valid locus tags
|
| 85 |
if locus_tag and locus_tag.lower() != "n/a":
|
| 86 |
+
# Get location info
|
| 87 |
+
start = int(feature.location.start)
|
| 88 |
+
end = int(feature.location.end)
|
| 89 |
+
strand = '+' if feature.location.strand == 1 else '-'
|
| 90 |
+
|
| 91 |
+
# Store feature info with full names
|
| 92 |
feature_entry = {
|
|
|
|
| 93 |
'feature_type': feature.type,
|
| 94 |
'chromosome': record.id,
|
| 95 |
+
'location': f"{start}:{end}({strand})",
|
| 96 |
+
'gene_name': feature.qualifiers.get('gene', ['N/A'])[0],
|
| 97 |
+
'description': feature.qualifiers.get('product',
|
| 98 |
+
feature.qualifiers.get('note', ['N/A']))[0],
|
| 99 |
+
'start': start,
|
| 100 |
+
'end': end
|
|
|
|
| 101 |
}
|
| 102 |
|
| 103 |
+
# Store in index
|
| 104 |
+
index_data['locus_tags'][locus_tag] = feature_entry
|
| 105 |
+
|
| 106 |
+
record_time = time.time() - record_start
|
| 107 |
+
if record_count % 100 == 0:
|
| 108 |
+
self.logger.debug(f"Processed {record_count} records, {feature_count} features. Last record time: {record_time:.2f}s")
|
| 109 |
|
| 110 |
+
# Save compressed index to file
|
| 111 |
save_start = time.time()
|
| 112 |
with open(self.index_file, 'wb') as f:
|
| 113 |
+
pickle.dump(index_data, f, protocol=pickle.HIGHEST_PROTOCOL)
|
| 114 |
save_time = time.time() - save_start
|
|
|
|
| 115 |
total_time = time.time() - start_time
|
| 116 |
|
| 117 |
self._index = index_data
|
|
|
|
| 138 |
with open(self.index_file, 'rb') as f:
|
| 139 |
self._index = pickle.load(f)
|
| 140 |
load_time = time.time() - start_time
|
| 141 |
+
print(f"Index file: {self._index}")
|
| 142 |
self.logger.debug(f"Index file loaded successfully in {load_time:.2f} seconds")
|
| 143 |
return True
|
| 144 |
|
|
|
|
| 149 |
def genbank_search(self, queries):
|
| 150 |
"""Search using the index file for better performance"""
|
| 151 |
try:
|
| 152 |
+
if not self.annotation_file_name or os.path.isdir(self.annotation_file_name):
|
| 153 |
+
self.logger.warning("No valid annotation file set")
|
| 154 |
+
return []
|
| 155 |
|
| 156 |
self.logger.debug(f"Searching in annotation file: {self.annotation_file_name}")
|
| 157 |
results_list = []
|
| 158 |
|
| 159 |
# Convert queries to lowercase set for faster lookup
|
| 160 |
queries = {q.lower() for q in queries}
|
| 161 |
+
self.logger.debug(f"Search queries: {queries}")
|
| 162 |
|
| 163 |
# Search through index
|
| 164 |
+
if hasattr(self, '_index') and 'locus_tags' in self._index:
|
| 165 |
+
# Search through features, filtering for CDS and gene types only
|
| 166 |
+
for locus_tag, feature_entry in self._index['locus_tags'].items():
|
| 167 |
+
# Safely get feature type with default value
|
| 168 |
+
feature_type = feature_entry.get('feature_type', '')
|
| 169 |
+
|
| 170 |
+
# Only process CDS and gene features
|
| 171 |
+
if feature_type not in ['CDS', 'gene']:
|
| 172 |
+
continue
|
| 173 |
+
|
| 174 |
# Check gene name, locus tag, and description
|
| 175 |
searchable_text = ' '.join([
|
| 176 |
+
feature_entry.get('gene_name', '').lower(),
|
| 177 |
+
locus_tag.lower(),
|
| 178 |
+
feature_entry.get('description', '').lower()
|
|
|
|
|
|
|
| 179 |
])
|
| 180 |
|
| 181 |
# Check if any query matches
|
| 182 |
if any(query in searchable_text for query in queries):
|
| 183 |
info = {
|
| 184 |
+
'feature_id': locus_tag,
|
| 185 |
+
'feature_name': feature_entry.get('gene_name', 'N/A'),
|
| 186 |
+
'feature_location': feature_entry.get('location', 'N/A'),
|
| 187 |
+
'feature_description': feature_entry.get('description', 'N/A')
|
| 188 |
}
|
| 189 |
+
results_list.append((feature_entry.get('chromosome', ''), info))
|
| 190 |
|
| 191 |
return results_list
|
| 192 |
|
| 193 |
except Exception as e:
|
| 194 |
self.logger.error(f"Error in genbank_search: {str(e)}")
|
| 195 |
+
self.logger.error(f"Stack trace: {traceback.format_exc()}") # Add stack trace for better debugging
|
| 196 |
raise
|
| 197 |
|
| 198 |
def get_gene_data(self, gene_identifier):
|
| 199 |
+
"""Get gene data using the optimized index and fetch sequence on demand"""
|
| 200 |
try:
|
| 201 |
if not gene_identifier:
|
| 202 |
return None
|
|
|
|
| 208 |
# Try exact match first
|
| 209 |
if gene_identifier in self._index['locus_tags']:
|
| 210 |
gene_info = self._index['locus_tags'][gene_identifier]
|
| 211 |
+
|
| 212 |
+
# Get sequence from file
|
| 213 |
+
sequence = self._get_sequence_for_gene(gene_info)
|
| 214 |
+
if sequence is None:
|
| 215 |
+
return None
|
| 216 |
+
|
| 217 |
+
# Use full names instead of shortened keys
|
| 218 |
+
expanded_info = {
|
| 219 |
+
'feature_type': gene_info['feature_type'],
|
| 220 |
+
'chromosome': gene_info['chromosome'],
|
| 221 |
+
'location': gene_info['location'],
|
| 222 |
+
'gene_name': gene_info['gene_name'],
|
| 223 |
+
'description': gene_info['description'],
|
| 224 |
+
'start': gene_info['start'],
|
| 225 |
+
'end': gene_info['end']
|
| 226 |
}
|
| 227 |
|
| 228 |
+
return {
|
| 229 |
+
'sequence': sequence,
|
| 230 |
+
'info': expanded_info
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
# Try case-insensitive match
|
| 234 |
for key, value in self._index['locus_tags'].items():
|
| 235 |
if str(key).lower() == gene_identifier:
|
| 236 |
+
sequence = self._get_sequence_for_gene(value)
|
| 237 |
+
if sequence is None:
|
| 238 |
+
return None
|
| 239 |
+
|
| 240 |
+
expanded_info = {
|
| 241 |
+
'feature_type': value['feature_type'],
|
| 242 |
+
'chromosome': value['chromosome'],
|
| 243 |
+
'location': value['location'],
|
| 244 |
+
'gene_name': value['gene_name'],
|
| 245 |
+
'description': value['description'],
|
| 246 |
+
'start': value['start'],
|
| 247 |
+
'end': value['end']
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
return {
|
| 251 |
+
'sequence': sequence,
|
| 252 |
+
'info': expanded_info
|
| 253 |
}
|
| 254 |
|
| 255 |
return None
|
| 256 |
+
|
| 257 |
except Exception as e:
|
| 258 |
self.logger.error(f"Error in get_gene_data: {str(e)}")
|
| 259 |
return None
|
| 260 |
|
| 261 |
+
def _get_sequence_for_gene(self, gene_info):
|
| 262 |
+
"""Get sequence for a gene from the GenBank file"""
|
| 263 |
+
try:
|
| 264 |
+
self.logger.debug(f"Getting sequence for gene info: {gene_info} in _get_sequence_for_gene")
|
| 265 |
+
# Parse the GenBank file and find the right record
|
| 266 |
+
for record in SeqIO.parse(self.annotation_file_name, "genbank"):
|
| 267 |
+
if record.id == gene_info['chromosome']: # Use full chromosome name
|
| 268 |
+
sequence = str(record.seq)
|
| 269 |
+
|
| 270 |
+
# Get sequence with padding
|
| 271 |
+
padding = 30
|
| 272 |
+
start = max(0, gene_info['start'] - padding)
|
| 273 |
+
end = min(len(sequence), gene_info['end'] + padding)
|
| 274 |
+
padded_sequence = sequence[start:end]
|
| 275 |
+
|
| 276 |
+
self.logger.debug(f"Padded sequence: {padded_sequence}")
|
| 277 |
+
|
| 278 |
+
return padded_sequence
|
| 279 |
+
|
| 280 |
+
return None
|
| 281 |
+
|
| 282 |
+
except Exception as e:
|
| 283 |
+
self.logger.error(f"Error getting sequence for gene: {str(e)}")
|
| 284 |
+
return None
|
| 285 |
+
|
| 286 |
@lru_cache(maxsize=1)
|
| 287 |
def _get_records(self):
|
| 288 |
"""Cache and return all records from the annotation file"""
|
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class CoTargetingModel:
|
| 2 |
+
def __init__(self, global_settings):
|
| 3 |
+
self.settings = global_settings
|
| 4 |
+
self.logger = global_settings.get_logger()
|
| 5 |
+
self.endo_data = {}
|
| 6 |
+
self.get_endo_data()
|
| 7 |
+
|
| 8 |
+
def get_endo_data(self):
|
| 9 |
+
"""Load endonuclease data from CASPERinfo file"""
|
| 10 |
+
try:
|
| 11 |
+
f = open(self.settings.get_casper_info_path())
|
| 12 |
+
while True:
|
| 13 |
+
line = f.readline()
|
| 14 |
+
if line.startswith('ENDONUCLEASES'):
|
| 15 |
+
while True:
|
| 16 |
+
line = f.readline()
|
| 17 |
+
if line[0] == "-":
|
| 18 |
+
break
|
| 19 |
+
line_tokened = line.split(";")
|
| 20 |
+
endo = line_tokened[0]
|
| 21 |
+
self.endo_data[endo] = ([line_tokened[2], line_tokened[3], line_tokened[4]], line_tokened[5])
|
| 22 |
+
break
|
| 23 |
+
f.close()
|
| 24 |
+
except Exception as e:
|
| 25 |
+
self.logger.error(f"Error loading endonuclease data: {str(e)}")
|
| 26 |
+
raise
|
| 27 |
+
|
| 28 |
+
def validate_endonucleases(self, endo_list):
|
| 29 |
+
"""Validate that selected endonucleases are compatible"""
|
| 30 |
+
try:
|
| 31 |
+
for endo1 in endo_list:
|
| 32 |
+
for endo2 in endo_list:
|
| 33 |
+
if endo1 == endo2:
|
| 34 |
+
continue
|
| 35 |
+
# Check gRNA length compatibility
|
| 36 |
+
endo1_len = sum([int(x) for x in self.endo_data[endo1][0]])
|
| 37 |
+
endo2_len = sum([int(x) for x in self.endo_data[endo2][0]])
|
| 38 |
+
|
| 39 |
+
# Check directionality compatibility
|
| 40 |
+
if (endo1_len != endo2_len or
|
| 41 |
+
self.endo_data[endo1][1] != self.endo_data[endo2][1]):
|
| 42 |
+
return False
|
| 43 |
+
return True
|
| 44 |
+
except Exception as e:
|
| 45 |
+
self.logger.error(f"Error validating endonucleases: {str(e)}")
|
| 46 |
+
return False
|
| 47 |
+
|
| 48 |
+
def format_endo_combination(self, endo_list):
|
| 49 |
+
"""Format selected endonucleases into combined string"""
|
| 50 |
+
return "|".join(endo_list)
|
|
@@ -159,25 +159,24 @@ class ConfigManager(QObject):
|
|
| 159 |
fields = line.strip().split(';')
|
| 160 |
print(fields)
|
| 161 |
if len(fields) == 10:
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
five_prime_length, seed_length, three_prime_length, direction = fields[4:8]
|
| 168 |
endonuclease_on_target_matrix = fields[8]
|
| 169 |
endonuclease_off_target_matrix = fields[9]
|
| 170 |
|
| 171 |
endonuclease_key = f"{endonuclease_abbreviation} - PAM: {pam_sequence}"
|
| 172 |
endonuclease_value = {
|
| 173 |
-
'endonuclease_organism': endonuclease_name,
|
| 174 |
'endonuclease_abbreviation': endonuclease_abbreviation,
|
| 175 |
-
'endonuclease_CRISPR_type': endonuclease_CRISPR_type,
|
| 176 |
'endonuclease_pam_sequence': pam_sequence,
|
| 177 |
'endonuclease_five_prime_length': five_prime_length,
|
| 178 |
'endonuclease_seed_length': seed_length,
|
| 179 |
'endonuclease_three_prime_length': three_prime_length,
|
| 180 |
'endonuclease_direction': direction,
|
|
|
|
|
|
|
| 181 |
'endonuclease_on_target_scoring': endonuclease_on_target_matrix,
|
| 182 |
'endonuclease_off_target_scoring': endonuclease_off_target_matrix
|
| 183 |
}
|
|
|
|
| 159 |
fields = line.strip().split(';')
|
| 160 |
print(fields)
|
| 161 |
if len(fields) == 10:
|
| 162 |
+
endonuclease_abbreviation = fields[0]
|
| 163 |
+
pam_sequence = fields[1]
|
| 164 |
+
five_prime_length, seed_length, three_prime_length, direction = fields[2:6]
|
| 165 |
+
endonuclease_name = fields[6]
|
| 166 |
+
endonuclease_CRISPR_type = fields[7]
|
|
|
|
| 167 |
endonuclease_on_target_matrix = fields[8]
|
| 168 |
endonuclease_off_target_matrix = fields[9]
|
| 169 |
|
| 170 |
endonuclease_key = f"{endonuclease_abbreviation} - PAM: {pam_sequence}"
|
| 171 |
endonuclease_value = {
|
|
|
|
| 172 |
'endonuclease_abbreviation': endonuclease_abbreviation,
|
|
|
|
| 173 |
'endonuclease_pam_sequence': pam_sequence,
|
| 174 |
'endonuclease_five_prime_length': five_prime_length,
|
| 175 |
'endonuclease_seed_length': seed_length,
|
| 176 |
'endonuclease_three_prime_length': three_prime_length,
|
| 177 |
'endonuclease_direction': direction,
|
| 178 |
+
'endonuclease_organism': endonuclease_name,
|
| 179 |
+
'endonuclease_CRISPR_type': endonuclease_CRISPR_type,
|
| 180 |
'endonuclease_on_target_scoring': endonuclease_on_target_matrix,
|
| 181 |
'endonuclease_off_target_scoring': endonuclease_off_target_matrix
|
| 182 |
}
|
|
@@ -3,19 +3,33 @@ from PyQt6.QtCore import QObject, pyqtSignal, QFileSystemWatcher
|
|
| 3 |
import sqlite3
|
| 4 |
from collections import Counter
|
| 5 |
import statistics
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
class DatabaseManager(QObject):
|
| 8 |
-
|
|
|
|
|
|
|
| 9 |
|
| 10 |
def __init__(self, logger, config_manager):
|
| 11 |
super().__init__()
|
| 12 |
self.logger = logger
|
| 13 |
self.config_manager = config_manager
|
| 14 |
self.db_path = None
|
| 15 |
-
self.file_watcher = QFileSystemWatcher()
|
| 16 |
self.file_watcher.directoryChanged.connect(self._on_directory_changed)
|
| 17 |
self.load_database_path()
|
| 18 |
self._update_watched_directory()
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
def load_database_path(self):
|
| 21 |
"""Load the database path from .env file or set default if empty."""
|
|
@@ -29,17 +43,20 @@ class DatabaseManager(QObject):
|
|
| 29 |
return self.db_path
|
| 30 |
|
| 31 |
def validate_db_path(self, path):
|
| 32 |
-
"""Validate that the given path exists and contains CSPR files
|
| 33 |
self.logger.debug(f"Validating DB path: {path}")
|
|
|
|
| 34 |
if not os.path.isdir(path):
|
| 35 |
self.logger.debug(f"Path is not a directory: {path}")
|
| 36 |
return False, "The selected path is not a directory."
|
| 37 |
-
|
| 38 |
-
|
|
|
|
| 39 |
self.logger.debug(f"Path {path} does not contain CSPR files")
|
| 40 |
return False, "The selected directory does not contain any CSPR files."
|
| 41 |
-
|
| 42 |
-
|
|
|
|
| 43 |
|
| 44 |
def save_db_path(self, path):
|
| 45 |
"""Set and save the database path."""
|
|
@@ -54,7 +71,7 @@ class DatabaseManager(QObject):
|
|
| 54 |
is_valid, message = self.validate_db_path(path)
|
| 55 |
if not is_valid:
|
| 56 |
self.logger.warning(f"Invalid database path: {path}")
|
| 57 |
-
self.
|
| 58 |
self.db_path = path
|
| 59 |
self.config_manager.set_env_value('CSPR_DB', path)
|
| 60 |
self._update_watched_directory()
|
|
@@ -66,13 +83,13 @@ class DatabaseManager(QObject):
|
|
| 66 |
try:
|
| 67 |
self.config_manager.set_env_value('CSPR_DB', path)
|
| 68 |
self.logger.info(f"Database path set and saved: {path}")
|
| 69 |
-
self.
|
| 70 |
self._update_watched_directory()
|
| 71 |
return True, "Database path saved successfully."
|
| 72 |
except Exception as e:
|
| 73 |
error_message = f"Error saving database path: {str(e)}"
|
| 74 |
self.logger.error(error_message)
|
| 75 |
-
self.
|
| 76 |
return False, error_message
|
| 77 |
|
| 78 |
def get_db_path(self):
|
|
@@ -108,61 +125,112 @@ class DatabaseManager(QObject):
|
|
| 108 |
return adjusted_path
|
| 109 |
|
| 110 |
def _update_watched_directory(self):
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
self.file_watcher.removePaths(self.file_watcher.directories())
|
| 114 |
if self.db_path and os.path.isdir(self.db_path):
|
| 115 |
self.file_watcher.addPath(self.db_path)
|
| 116 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
|
| 118 |
-
def
|
| 119 |
-
"""
|
| 120 |
-
self.
|
|
|
|
| 121 |
|
| 122 |
-
|
| 123 |
-
is_valid, message = self.validate_db_path(path)
|
| 124 |
|
| 125 |
-
#
|
| 126 |
-
|
| 127 |
-
|
| 128 |
|
| 129 |
-
|
| 130 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
|
| 132 |
-
|
| 133 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
|
| 135 |
def _get_cspr_files(self):
|
| 136 |
-
"""Get a list of CSPR files in the current database directory
|
| 137 |
if not self.db_path or not os.path.isdir(self.db_path):
|
| 138 |
return []
|
| 139 |
-
return [f for f in os.listdir(self.db_path)
|
|
|
|
| 140 |
|
| 141 |
def _get_gbff_files(self):
|
| 142 |
-
"""Get a list of GBFF files in the database directory
|
| 143 |
if not self.db_path or not os.path.isdir(self.db_path):
|
| 144 |
return []
|
| 145 |
gbff_path = os.path.join(self.db_path, 'GBFF')
|
| 146 |
if not os.path.exists(gbff_path):
|
| 147 |
return []
|
| 148 |
-
return [f for f in os.listdir(gbff_path)
|
|
|
|
| 149 |
|
| 150 |
def check_db_state(self):
|
| 151 |
-
"""Check the current state of the database and emit signals if
|
| 152 |
self.logger.debug("Checking database state")
|
| 153 |
if not self.db_path:
|
| 154 |
self.load_database_path()
|
| 155 |
|
|
|
|
| 156 |
is_valid, message = self.validate_db_path(self.db_path)
|
| 157 |
-
self.logger.debug(f"Database state: valid={is_valid}, message={message}")
|
| 158 |
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
message = f"Database is valid. Contains {len(cspr_files)} CSPR files and {len(gbff_files)} GBFF files."
|
| 163 |
|
| 164 |
-
|
| 165 |
-
self.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 166 |
|
| 167 |
def get_organisms_and_endos(self):
|
| 168 |
"""Get mapping of organisms to their endonucleases and files"""
|
|
@@ -180,8 +248,8 @@ class DatabaseManager(QObject):
|
|
| 180 |
for file in cspr_files:
|
| 181 |
try:
|
| 182 |
# Parse filename
|
| 183 |
-
newname = file[0:-5]
|
| 184 |
-
|
| 185 |
|
| 186 |
# Read organism name from first line of CSPR file
|
| 187 |
file_path = os.path.join(self.db_path, file)
|
|
@@ -191,17 +259,17 @@ class DatabaseManager(QObject):
|
|
| 191 |
|
| 192 |
# Store file mappings
|
| 193 |
if species in organisms_to_files:
|
| 194 |
-
organisms_to_files[species][
|
| 195 |
else:
|
| 196 |
organisms_to_files[species] = {}
|
| 197 |
-
organisms_to_files[species][
|
| 198 |
|
| 199 |
# Store endonuclease mappings
|
| 200 |
if species in organisms_to_endos:
|
| 201 |
-
if
|
| 202 |
-
organisms_to_endos[species].append(
|
| 203 |
else:
|
| 204 |
-
organisms_to_endos[species] = [
|
| 205 |
|
| 206 |
except Exception as e:
|
| 207 |
self.logger.error(f"Error processing file {file}: {str(e)}")
|
|
|
|
| 3 |
import sqlite3
|
| 4 |
from collections import Counter
|
| 5 |
import statistics
|
| 6 |
+
from enum import Enum
|
| 7 |
+
from typing import Set, Dict, List, Tuple
|
| 8 |
+
|
| 9 |
+
class FileChangeType(Enum):
|
| 10 |
+
CSPR_ADDED = "cspr_added"
|
| 11 |
+
CSPR_REMOVED = "cspr_removed"
|
| 12 |
+
GBFF_ADDED = "gbff_added"
|
| 13 |
+
GBFF_REMOVED = "gbff_removed"
|
| 14 |
+
OTHER = "other"
|
| 15 |
|
| 16 |
class DatabaseManager(QObject):
|
| 17 |
+
db_files_changed = pyqtSignal(dict) # Emits a dict of FileChangeType: List[str]
|
| 18 |
+
db_validation_changed = pyqtSignal(bool, str) # Emits validation state and message
|
| 19 |
+
db_state_changed = pyqtSignal(bool, str, dict) # Emits (is_valid, message, changes)
|
| 20 |
|
| 21 |
def __init__(self, logger, config_manager):
|
| 22 |
super().__init__()
|
| 23 |
self.logger = logger
|
| 24 |
self.config_manager = config_manager
|
| 25 |
self.db_path = None
|
| 26 |
+
self.file_watcher = QFileSystemWatcher()
|
| 27 |
self.file_watcher.directoryChanged.connect(self._on_directory_changed)
|
| 28 |
self.load_database_path()
|
| 29 |
self._update_watched_directory()
|
| 30 |
+
|
| 31 |
+
self._last_cspr_files: Set[str] = set(self._get_cspr_files())
|
| 32 |
+
self._last_gbff_files: Set[str] = set(self._get_gbff_files())
|
| 33 |
|
| 34 |
def load_database_path(self):
|
| 35 |
"""Load the database path from .env file or set default if empty."""
|
|
|
|
| 43 |
return self.db_path
|
| 44 |
|
| 45 |
def validate_db_path(self, path):
|
| 46 |
+
"""Validate that the given path exists and contains CSPR files"""
|
| 47 |
self.logger.debug(f"Validating DB path: {path}")
|
| 48 |
+
|
| 49 |
if not os.path.isdir(path):
|
| 50 |
self.logger.debug(f"Path is not a directory: {path}")
|
| 51 |
return False, "The selected path is not a directory."
|
| 52 |
+
|
| 53 |
+
cspr_files = self._get_cspr_files()
|
| 54 |
+
if not cspr_files:
|
| 55 |
self.logger.debug(f"Path {path} does not contain CSPR files")
|
| 56 |
return False, "The selected directory does not contain any CSPR files."
|
| 57 |
+
|
| 58 |
+
self.logger.debug(f"Path {path} is valid and contains {len(cspr_files)} CSPR files")
|
| 59 |
+
return True, f"Valid database path with {len(cspr_files)} CSPR files"
|
| 60 |
|
| 61 |
def save_db_path(self, path):
|
| 62 |
"""Set and save the database path."""
|
|
|
|
| 71 |
is_valid, message = self.validate_db_path(path)
|
| 72 |
if not is_valid:
|
| 73 |
self.logger.warning(f"Invalid database path: {path}")
|
| 74 |
+
self.db_validation_changed.emit(False, message)
|
| 75 |
self.db_path = path
|
| 76 |
self.config_manager.set_env_value('CSPR_DB', path)
|
| 77 |
self._update_watched_directory()
|
|
|
|
| 83 |
try:
|
| 84 |
self.config_manager.set_env_value('CSPR_DB', path)
|
| 85 |
self.logger.info(f"Database path set and saved: {path}")
|
| 86 |
+
self.db_validation_changed.emit(True, "Database path saved successfully.")
|
| 87 |
self._update_watched_directory()
|
| 88 |
return True, "Database path saved successfully."
|
| 89 |
except Exception as e:
|
| 90 |
error_message = f"Error saving database path: {str(e)}"
|
| 91 |
self.logger.error(error_message)
|
| 92 |
+
self.db_validation_changed.emit(False, error_message)
|
| 93 |
return False, error_message
|
| 94 |
|
| 95 |
def get_db_path(self):
|
|
|
|
| 125 |
return adjusted_path
|
| 126 |
|
| 127 |
def _update_watched_directory(self):
|
| 128 |
+
self.file_watcher.removePaths(self.file_watcher.directories())
|
| 129 |
+
|
|
|
|
| 130 |
if self.db_path and os.path.isdir(self.db_path):
|
| 131 |
self.file_watcher.addPath(self.db_path)
|
| 132 |
+
|
| 133 |
+
# Also watch GBFF subdirectory if it exists
|
| 134 |
+
gbff_path = os.path.join(self.db_path, 'GBFF')
|
| 135 |
+
if os.path.isdir(gbff_path):
|
| 136 |
+
self.file_watcher.addPath(gbff_path)
|
| 137 |
+
|
| 138 |
+
self.logger.debug(f"Now watching directories: {self.file_watcher.directories()}")
|
| 139 |
|
| 140 |
+
def _detect_file_changes(self) -> Dict[FileChangeType, List[str]]:
|
| 141 |
+
"""Detect what files have changed and categorize the changes"""
|
| 142 |
+
current_cspr_files = set(self._get_cspr_files())
|
| 143 |
+
current_gbff_files = set(self._get_gbff_files())
|
| 144 |
|
| 145 |
+
changes = {}
|
|
|
|
| 146 |
|
| 147 |
+
# Detect CSPR changes
|
| 148 |
+
cspr_added = current_cspr_files - self._last_cspr_files
|
| 149 |
+
cspr_removed = self._last_cspr_files - current_cspr_files
|
| 150 |
|
| 151 |
+
if cspr_added:
|
| 152 |
+
changes[FileChangeType.CSPR_ADDED] = list(cspr_added)
|
| 153 |
+
if cspr_removed:
|
| 154 |
+
changes[FileChangeType.CSPR_REMOVED] = list(cspr_removed)
|
| 155 |
+
|
| 156 |
+
# Detect GBFF changes
|
| 157 |
+
gbff_added = current_gbff_files - self._last_gbff_files
|
| 158 |
+
gbff_removed = self._last_gbff_files - current_gbff_files
|
| 159 |
+
|
| 160 |
+
if gbff_added:
|
| 161 |
+
changes[FileChangeType.GBFF_ADDED] = list(gbff_added)
|
| 162 |
+
if gbff_removed:
|
| 163 |
+
changes[FileChangeType.GBFF_REMOVED] = list(gbff_removed)
|
| 164 |
+
|
| 165 |
+
# Update last known state
|
| 166 |
+
self._last_cspr_files = current_cspr_files
|
| 167 |
+
self._last_gbff_files = current_gbff_files
|
| 168 |
|
| 169 |
+
return changes
|
| 170 |
+
|
| 171 |
+
def _on_directory_changed(self, path):
|
| 172 |
+
"""Handle changes in the watched directory"""
|
| 173 |
+
try:
|
| 174 |
+
self.logger.debug(f"Detected change in directory: {path}")
|
| 175 |
+
|
| 176 |
+
# Detect specific changes
|
| 177 |
+
changes = self._detect_file_changes()
|
| 178 |
+
|
| 179 |
+
if changes: # Only emit if there are actual changes
|
| 180 |
+
self.logger.debug(f"Detected file changes: {changes}")
|
| 181 |
+
|
| 182 |
+
# Get validation state
|
| 183 |
+
is_valid, message = self.validate_db_path(path)
|
| 184 |
+
|
| 185 |
+
# Emit separate signals
|
| 186 |
+
self.db_validation_changed.emit(is_valid, message)
|
| 187 |
+
self.db_files_changed.emit(changes)
|
| 188 |
+
|
| 189 |
+
# Emit combined signal for components that want everything
|
| 190 |
+
self.db_state_changed.emit(is_valid, message, changes)
|
| 191 |
+
|
| 192 |
+
self.logger.info(f"Database state updated - Valid: {is_valid}, Changes: {changes}")
|
| 193 |
+
else:
|
| 194 |
+
self.logger.debug("No relevant file changes detected")
|
| 195 |
+
|
| 196 |
+
except Exception as e:
|
| 197 |
+
self.logger.error(f"Error handling directory change: {str(e)}")
|
| 198 |
|
| 199 |
def _get_cspr_files(self):
|
| 200 |
+
"""Get a list of CSPR files in the current database directory"""
|
| 201 |
if not self.db_path or not os.path.isdir(self.db_path):
|
| 202 |
return []
|
| 203 |
+
return [f for f in os.listdir(self.db_path)
|
| 204 |
+
if f.endswith('.cspr')]
|
| 205 |
|
| 206 |
def _get_gbff_files(self):
|
| 207 |
+
"""Get a list of GBFF files in the database directory"""
|
| 208 |
if not self.db_path or not os.path.isdir(self.db_path):
|
| 209 |
return []
|
| 210 |
gbff_path = os.path.join(self.db_path, 'GBFF')
|
| 211 |
if not os.path.exists(gbff_path):
|
| 212 |
return []
|
| 213 |
+
return [f for f in os.listdir(gbff_path)
|
| 214 |
+
if f.endswith('.gbff')]
|
| 215 |
|
| 216 |
def check_db_state(self):
|
| 217 |
+
"""Check the current state of the database and emit signals if needed."""
|
| 218 |
self.logger.debug("Checking database state")
|
| 219 |
if not self.db_path:
|
| 220 |
self.load_database_path()
|
| 221 |
|
| 222 |
+
# Get validation state
|
| 223 |
is_valid, message = self.validate_db_path(self.db_path)
|
|
|
|
| 224 |
|
| 225 |
+
# Detect any changes since last check
|
| 226 |
+
changes = self._detect_file_changes()
|
|
|
|
|
|
|
| 227 |
|
| 228 |
+
# Emit signals
|
| 229 |
+
self.db_validation_changed.emit(is_valid, message)
|
| 230 |
+
if changes:
|
| 231 |
+
self.db_files_changed.emit(changes)
|
| 232 |
+
|
| 233 |
+
self.logger.info(f"Database state checked - Valid: {is_valid}, Changes: {changes}")
|
| 234 |
|
| 235 |
def get_organisms_and_endos(self):
|
| 236 |
"""Get mapping of organisms to their endonucleases and files"""
|
|
|
|
| 248 |
for file in cspr_files:
|
| 249 |
try:
|
| 250 |
# Parse filename
|
| 251 |
+
newname = file[0:-5]
|
| 252 |
+
endonuclease = newname[newname.rfind("_") + 1:]
|
| 253 |
|
| 254 |
# Read organism name from first line of CSPR file
|
| 255 |
file_path = os.path.join(self.db_path, file)
|
|
|
|
| 259 |
|
| 260 |
# Store file mappings
|
| 261 |
if species in organisms_to_files:
|
| 262 |
+
organisms_to_files[species][endonuclease] = [file, file.replace(".cspr", "_repeats.db")]
|
| 263 |
else:
|
| 264 |
organisms_to_files[species] = {}
|
| 265 |
+
organisms_to_files[species][endonuclease] = [file, file.replace(".cspr", "_repeats.db")]
|
| 266 |
|
| 267 |
# Store endonuclease mappings
|
| 268 |
if species in organisms_to_endos:
|
| 269 |
+
if endonuclease not in organisms_to_endos[species]:
|
| 270 |
+
organisms_to_endos[species].append(endonuclease)
|
| 271 |
else:
|
| 272 |
+
organisms_to_endos[species] = [endonuclease]
|
| 273 |
|
| 274 |
except Exception as e:
|
| 275 |
self.logger.error(f"Error processing file {file}: {str(e)}")
|
|
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from typing import Dict, List
|
| 3 |
+
from utils.sequence_utils import get_table_headers
|
| 4 |
+
|
| 5 |
+
class ExportSelectedgRNAsModel:
|
| 6 |
+
def __init__(self, global_settings):
|
| 7 |
+
self.global_settings = global_settings
|
| 8 |
+
self.logger = global_settings.get_logger()
|
| 9 |
+
self.data = {
|
| 10 |
+
'selected_items': [],
|
| 11 |
+
'window_type': "",
|
| 12 |
+
'has_locus_tag': False,
|
| 13 |
+
'has_gene_name': False
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
def set_export_data(self, items: List, window_type: str) -> None:
|
| 17 |
+
"""Set the selected items and window type"""
|
| 18 |
+
try:
|
| 19 |
+
self.data['selected_items'] = items
|
| 20 |
+
self.data['window_type'] = window_type
|
| 21 |
+
self.logger.debug(f"Set export data: {len(items)} items, window type: {window_type}")
|
| 22 |
+
except Exception as e:
|
| 23 |
+
self.logger.error(f"Error setting export data: {str(e)}")
|
| 24 |
+
raise
|
| 25 |
+
|
| 26 |
+
def get_headers(self) -> List[str]:
|
| 27 |
+
"""Get appropriate headers based on window type"""
|
| 28 |
+
try:
|
| 29 |
+
if self.data['window_type'] == "Multitargeting":
|
| 30 |
+
return self._get_multitargeting_headers()
|
| 31 |
+
elif self.data['window_type'] == "Population Analysis":
|
| 32 |
+
return self._get_population_analysis_headers()
|
| 33 |
+
else:
|
| 34 |
+
return self._get_view_targets_headers()
|
| 35 |
+
except Exception as e:
|
| 36 |
+
self.logger.error(f"Error getting headers: {str(e)}")
|
| 37 |
+
raise
|
| 38 |
+
|
| 39 |
+
def _get_multitargeting_headers(self) -> List[str]:
|
| 40 |
+
"""Get headers for multitargeting window"""
|
| 41 |
+
try:
|
| 42 |
+
headers = [
|
| 43 |
+
"Seed", "Total Repeats", "Avg. Repeats/Scaffold",
|
| 44 |
+
"Consensus Sequence", "% Consensus", "Score", "PAM", "Strand"
|
| 45 |
+
]
|
| 46 |
+
|
| 47 |
+
insertion_index = headers.index("Consensus Sequence")
|
| 48 |
+
headers.insert(insertion_index + 1, "Full Sequence")
|
| 49 |
+
|
| 50 |
+
self.logger.debug(f"Multitargeting headers: {headers}")
|
| 51 |
+
return headers
|
| 52 |
+
|
| 53 |
+
except Exception as e:
|
| 54 |
+
self.logger.error(f"Error getting multitargeting headers: {str(e)}")
|
| 55 |
+
raise
|
| 56 |
+
|
| 57 |
+
def _get_population_analysis_headers(self) -> List[str]:
|
| 58 |
+
"""Get headers for population analysis window"""
|
| 59 |
+
try:
|
| 60 |
+
headers = [
|
| 61 |
+
"Seed", "% Coverage", "Total Repeats", "Avg. Repeats/Scaffold",
|
| 62 |
+
"Consensus Sequence", "% Consensus", "Score", "PAM", "Strand"
|
| 63 |
+
]
|
| 64 |
+
|
| 65 |
+
insertion_index = headers.index("Consensus Sequence")
|
| 66 |
+
headers.insert(insertion_index + 1, "Full Sequence")
|
| 67 |
+
|
| 68 |
+
self.logger.debug(f"Population Analysis headers: {headers}")
|
| 69 |
+
return headers
|
| 70 |
+
|
| 71 |
+
except Exception as e:
|
| 72 |
+
self.logger.error(f"Error getting population analysis headers: {str(e)}")
|
| 73 |
+
raise
|
| 74 |
+
|
| 75 |
+
def _get_view_targets_headers(self) -> List[str]:
|
| 76 |
+
"""Get headers for view targets window"""
|
| 77 |
+
try:
|
| 78 |
+
headers = [
|
| 79 |
+
"Location", "Endonuclease", "Sequence", "Strand",
|
| 80 |
+
"PAM", "Score", "Off-Target"
|
| 81 |
+
]
|
| 82 |
+
|
| 83 |
+
sequence_index = headers.index("Sequence")
|
| 84 |
+
headers.insert(sequence_index + 1, "Full Sequence")
|
| 85 |
+
|
| 86 |
+
if self.data['selected_items']:
|
| 87 |
+
first_target = self.data['selected_items'][0]
|
| 88 |
+
|
| 89 |
+
if 'locus_tag' in first_target or 'feature_id' in first_target:
|
| 90 |
+
headers.append("Locus_Tag")
|
| 91 |
+
self.data['has_locus_tag'] = True
|
| 92 |
+
|
| 93 |
+
if 'gene_name' in first_target or 'feature_name' in first_target:
|
| 94 |
+
headers.append("Gene_Name")
|
| 95 |
+
self.data['has_gene_name'] = True
|
| 96 |
+
|
| 97 |
+
return headers
|
| 98 |
+
|
| 99 |
+
except Exception as e:
|
| 100 |
+
self.logger.error(f"Error getting view targets headers: {str(e)}")
|
| 101 |
+
raise
|
| 102 |
+
|
| 103 |
+
def get_file_extension(self, delimiter: str) -> str:
|
| 104 |
+
"""Get appropriate file extension based on delimiter"""
|
| 105 |
+
if delimiter == ",":
|
| 106 |
+
return ".csv"
|
| 107 |
+
elif delimiter == r"\t":
|
| 108 |
+
return ".tsv"
|
| 109 |
+
return ".txt"
|
| 110 |
+
|
| 111 |
+
def get_full_path(self, directory: str, filename: str, delimiter: str) -> str:
|
| 112 |
+
"""Construct full file path"""
|
| 113 |
+
if '.' in filename:
|
| 114 |
+
return os.path.join(directory, filename)
|
| 115 |
+
extension = self.get_file_extension(delimiter)
|
| 116 |
+
return os.path.join(directory, filename + extension)
|
|
@@ -4,12 +4,14 @@ from models.CSPRparser import CSPRparser
|
|
| 4 |
from models.AnnotationParser import AnnotationParser
|
| 5 |
import os
|
| 6 |
from functools import lru_cache
|
|
|
|
|
|
|
| 7 |
|
| 8 |
class FindTargetsModel(HomeWindowModel):
|
| 9 |
def __init__(self, global_settings):
|
| 10 |
super().__init__(global_settings)
|
| 11 |
self.results = {}
|
| 12 |
-
self._parser_cache = {}
|
| 13 |
self.global_settings.annotation_file_changed.connect(self._on_annotation_file_changed)
|
| 14 |
|
| 15 |
def _on_annotation_file_changed(self, new_annotation_file):
|
|
@@ -27,26 +29,15 @@ class FindTargetsModel(HomeWindowModel):
|
|
| 27 |
def find_targets(self, input_data):
|
| 28 |
self.global_settings.logger.debug(f"Received input data: {input_data}")
|
| 29 |
|
| 30 |
-
start_time = time.time()
|
| 31 |
-
|
| 32 |
organism = input_data['organism']
|
| 33 |
endo = input_data['endonuclease']
|
| 34 |
org_files = self.get_organism_to_files()
|
| 35 |
|
| 36 |
-
# Validate input data
|
| 37 |
-
validate_start = time.time()
|
| 38 |
self._validate_input(organism, endo, org_files)
|
| 39 |
-
validate_time = time.time() - validate_start
|
| 40 |
-
self.global_settings.logger.debug(f"Validation time: {validate_time:.2f} seconds")
|
| 41 |
|
| 42 |
-
# Get file path and parser
|
| 43 |
-
parser_start = time.time()
|
| 44 |
file_path = os.path.join(self.global_settings.get_db_path(), org_files[organism][endo][0])
|
| 45 |
parser = self._get_parser(file_path)
|
| 46 |
-
parser_time = time.time() - parser_start
|
| 47 |
-
self.global_settings.logger.debug(f"Parser initialization time: {parser_time:.2f} seconds")
|
| 48 |
|
| 49 |
-
# Use dictionary for faster lookup
|
| 50 |
search_types = {
|
| 51 |
'feature': self.find_targets_by_feature,
|
| 52 |
'position': self.find_targets_by_position,
|
|
@@ -59,19 +50,11 @@ class FindTargetsModel(HomeWindowModel):
|
|
| 59 |
self.global_settings.logger.error(error_msg)
|
| 60 |
raise ValueError(error_msg)
|
| 61 |
|
| 62 |
-
# Perform the search
|
| 63 |
-
search_start = time.time()
|
| 64 |
self.results = search_func(parser, input_data)
|
| 65 |
-
search_time = time.time() - search_start
|
| 66 |
-
self.global_settings.logger.debug(f"Search execution time: {search_time:.2f} seconds")
|
| 67 |
-
|
| 68 |
-
total_time = time.time() - start_time
|
| 69 |
-
self.global_settings.logger.debug(f"Total find_targets time: {total_time:.2f} seconds")
|
| 70 |
|
| 71 |
return self.results
|
| 72 |
|
| 73 |
def _validate_input(self, organism, endo, org_files):
|
| 74 |
-
"""Validate input parameters"""
|
| 75 |
if organism not in org_files:
|
| 76 |
error_msg = f"Organism '{organism}' not found in the database. Available organisms: {list(org_files.keys())}"
|
| 77 |
self.global_settings.logger.error(error_msg)
|
|
@@ -83,47 +66,37 @@ class FindTargetsModel(HomeWindowModel):
|
|
| 83 |
raise ValueError(error_msg)
|
| 84 |
|
| 85 |
def find_targets_by_feature(self, parser, input_data):
|
| 86 |
-
"""Search for features using the indexed annotation parser"""
|
| 87 |
try:
|
| 88 |
-
start_time = time.time()
|
| 89 |
-
|
| 90 |
-
# Get annotation file from input data or global settings
|
| 91 |
annotation_file = (input_data.get('annotation_file') or
|
| 92 |
self.global_settings.get_current_annotation_file())
|
| 93 |
|
| 94 |
search_query = input_data['search_query'].strip()
|
| 95 |
|
| 96 |
-
# Create new annotation parser instance
|
| 97 |
-
parser_start = time.time()
|
| 98 |
annotation_parser = AnnotationParser(self.global_settings)
|
| 99 |
annotation_file_path = os.path.join(self.global_settings.get_db_path(), 'GBFF', annotation_file)
|
| 100 |
annotation_parser.set_annotation_file(annotation_file_path)
|
| 101 |
-
parser_time = time.time() - parser_start
|
| 102 |
-
self.global_settings.logger.debug(f"Annotation parser initialization time: {parser_time:.2f} seconds")
|
| 103 |
|
| 104 |
-
# Use indexed search
|
| 105 |
-
search_start = time.time()
|
| 106 |
results_list = annotation_parser.genbank_search([search_query])
|
| 107 |
-
search_time = time.time() - search_start
|
| 108 |
-
self.global_settings.logger.debug(f"Genbank search time: {search_time:.2f} seconds")
|
| 109 |
|
| 110 |
-
# Format results
|
| 111 |
-
format_start = time.time()
|
| 112 |
formatted_results = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
for record_id, feature_info in results_list:
|
| 114 |
-
# Extract start and end from feature_location
|
| 115 |
location = feature_info['feature_location']
|
| 116 |
start_end = location.split('(')[0] # Get part before the strand
|
| 117 |
start, end = map(int, start_end.split(':'))
|
| 118 |
|
| 119 |
-
|
| 120 |
-
chrom_num = record_id.split('.')[-1] if '.' in record_id else '1'
|
| 121 |
|
| 122 |
-
# Create target info with feature_id and chromosome number
|
| 123 |
target_info = {
|
| 124 |
'feature_type': 'CDS',
|
| 125 |
-
'chromosome': chrom_num,
|
| 126 |
-
'full_chromosome': record_id,
|
| 127 |
'feature_id': feature_info['feature_id'],
|
| 128 |
'feature_name': feature_info['feature_name'],
|
| 129 |
'feature_description': feature_info['feature_description'],
|
|
@@ -134,22 +107,10 @@ class FindTargetsModel(HomeWindowModel):
|
|
| 134 |
'endonuclease': input_data['endonuclease']
|
| 135 |
}
|
| 136 |
|
| 137 |
-
# Debug log the target info
|
| 138 |
self.global_settings.logger.debug(f"Created target info: {target_info}")
|
| 139 |
|
| 140 |
formatted_results.append(target_info)
|
| 141 |
|
| 142 |
-
format_time = time.time() - format_start
|
| 143 |
-
self.global_settings.logger.debug(f"Result formatting time: {format_time:.2f} seconds")
|
| 144 |
-
|
| 145 |
-
# Debug log sample results
|
| 146 |
-
if formatted_results:
|
| 147 |
-
self.global_settings.logger.debug(f"Sample formatted result: {formatted_results[0]}")
|
| 148 |
-
self.global_settings.logger.debug(f"Feature IDs present: {[r['feature_id'] for r in formatted_results[:5]]}")
|
| 149 |
-
|
| 150 |
-
total_time = time.time() - start_time
|
| 151 |
-
self.global_settings.logger.debug(f"Total find_targets_by_feature time: {total_time:.2f} seconds")
|
| 152 |
-
|
| 153 |
return formatted_results
|
| 154 |
|
| 155 |
except Exception as e:
|
|
@@ -157,27 +118,243 @@ class FindTargetsModel(HomeWindowModel):
|
|
| 157 |
raise
|
| 158 |
|
| 159 |
def find_targets_by_position(self, parser, input_data):
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 166 |
|
| 167 |
def find_targets_by_sequence(self, parser, input_data):
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
return []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 181 |
|
| 182 |
def _format_results(self, targets):
|
| 183 |
formatted_results = []
|
|
|
|
| 4 |
from models.AnnotationParser import AnnotationParser
|
| 5 |
import os
|
| 6 |
from functools import lru_cache
|
| 7 |
+
import traceback
|
| 8 |
+
from Bio import SeqIO
|
| 9 |
|
| 10 |
class FindTargetsModel(HomeWindowModel):
|
| 11 |
def __init__(self, global_settings):
|
| 12 |
super().__init__(global_settings)
|
| 13 |
self.results = {}
|
| 14 |
+
self._parser_cache = {}
|
| 15 |
self.global_settings.annotation_file_changed.connect(self._on_annotation_file_changed)
|
| 16 |
|
| 17 |
def _on_annotation_file_changed(self, new_annotation_file):
|
|
|
|
| 29 |
def find_targets(self, input_data):
|
| 30 |
self.global_settings.logger.debug(f"Received input data: {input_data}")
|
| 31 |
|
|
|
|
|
|
|
| 32 |
organism = input_data['organism']
|
| 33 |
endo = input_data['endonuclease']
|
| 34 |
org_files = self.get_organism_to_files()
|
| 35 |
|
|
|
|
|
|
|
| 36 |
self._validate_input(organism, endo, org_files)
|
|
|
|
|
|
|
| 37 |
|
|
|
|
|
|
|
| 38 |
file_path = os.path.join(self.global_settings.get_db_path(), org_files[organism][endo][0])
|
| 39 |
parser = self._get_parser(file_path)
|
|
|
|
|
|
|
| 40 |
|
|
|
|
| 41 |
search_types = {
|
| 42 |
'feature': self.find_targets_by_feature,
|
| 43 |
'position': self.find_targets_by_position,
|
|
|
|
| 50 |
self.global_settings.logger.error(error_msg)
|
| 51 |
raise ValueError(error_msg)
|
| 52 |
|
|
|
|
|
|
|
| 53 |
self.results = search_func(parser, input_data)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
return self.results
|
| 56 |
|
| 57 |
def _validate_input(self, organism, endo, org_files):
|
|
|
|
| 58 |
if organism not in org_files:
|
| 59 |
error_msg = f"Organism '{organism}' not found in the database. Available organisms: {list(org_files.keys())}"
|
| 60 |
self.global_settings.logger.error(error_msg)
|
|
|
|
| 66 |
raise ValueError(error_msg)
|
| 67 |
|
| 68 |
def find_targets_by_feature(self, parser, input_data):
|
|
|
|
| 69 |
try:
|
|
|
|
|
|
|
|
|
|
| 70 |
annotation_file = (input_data.get('annotation_file') or
|
| 71 |
self.global_settings.get_current_annotation_file())
|
| 72 |
|
| 73 |
search_query = input_data['search_query'].strip()
|
| 74 |
|
|
|
|
|
|
|
| 75 |
annotation_parser = AnnotationParser(self.global_settings)
|
| 76 |
annotation_file_path = os.path.join(self.global_settings.get_db_path(), 'GBFF', annotation_file)
|
| 77 |
annotation_parser.set_annotation_file(annotation_file_path)
|
|
|
|
|
|
|
| 78 |
|
|
|
|
|
|
|
| 79 |
results_list = annotation_parser.genbank_search([search_query])
|
|
|
|
|
|
|
| 80 |
|
|
|
|
|
|
|
| 81 |
formatted_results = []
|
| 82 |
+
|
| 83 |
+
chrom_mapping = {}
|
| 84 |
+
chrom_count = 0
|
| 85 |
+
for record in SeqIO.parse(annotation_file_path, "genbank"):
|
| 86 |
+
chrom_count += 1
|
| 87 |
+
chrom_mapping[record.id] = str(chrom_count)
|
| 88 |
+
|
| 89 |
for record_id, feature_info in results_list:
|
|
|
|
| 90 |
location = feature_info['feature_location']
|
| 91 |
start_end = location.split('(')[0] # Get part before the strand
|
| 92 |
start, end = map(int, start_end.split(':'))
|
| 93 |
|
| 94 |
+
chrom_num = chrom_mapping.get(record_id, '1')
|
|
|
|
| 95 |
|
|
|
|
| 96 |
target_info = {
|
| 97 |
'feature_type': 'CDS',
|
| 98 |
+
'chromosome': chrom_num,
|
| 99 |
+
'full_chromosome': record_id,
|
| 100 |
'feature_id': feature_info['feature_id'],
|
| 101 |
'feature_name': feature_info['feature_name'],
|
| 102 |
'feature_description': feature_info['feature_description'],
|
|
|
|
| 107 |
'endonuclease': input_data['endonuclease']
|
| 108 |
}
|
| 109 |
|
|
|
|
| 110 |
self.global_settings.logger.debug(f"Created target info: {target_info}")
|
| 111 |
|
| 112 |
formatted_results.append(target_info)
|
| 113 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
return formatted_results
|
| 115 |
|
| 116 |
except Exception as e:
|
|
|
|
| 118 |
raise
|
| 119 |
|
| 120 |
def find_targets_by_position(self, parser, input_data):
|
| 121 |
+
try:
|
| 122 |
+
queries = input_data['search_query'].strip().split('\n')
|
| 123 |
+
all_results = []
|
| 124 |
+
|
| 125 |
+
for query in queries:
|
| 126 |
+
try:
|
| 127 |
+
chrom, start, end = map(int, query.strip().split(','))
|
| 128 |
+
|
| 129 |
+
# Get full chromosome ID by counting carets
|
| 130 |
+
full_chrom = None
|
| 131 |
+
chrom_count = 0
|
| 132 |
+
|
| 133 |
+
# Get annotation file path
|
| 134 |
+
annotation_file = self.global_settings.get_current_annotation_file()
|
| 135 |
+
annotation_path = os.path.join(self.global_settings.get_db_path(), 'GBFF', annotation_file)
|
| 136 |
+
|
| 137 |
+
# Find the full chromosome ID by position
|
| 138 |
+
for record in SeqIO.parse(annotation_path, "genbank"):
|
| 139 |
+
chrom_count += 1
|
| 140 |
+
if chrom_count == chrom: # Match based on position
|
| 141 |
+
full_chrom = record.id
|
| 142 |
+
self.logger.debug(f"Found chromosome {chrom} as {full_chrom}")
|
| 143 |
+
break
|
| 144 |
+
|
| 145 |
+
if not full_chrom:
|
| 146 |
+
self.logger.warning(f"Could not find chromosome at position {chrom}")
|
| 147 |
+
continue
|
| 148 |
+
|
| 149 |
+
# Create target info with proper formatting
|
| 150 |
+
position_name = f"chrom {chrom}, start: {start}, end: {end}"
|
| 151 |
+
target_info = [{
|
| 152 |
+
'start': start,
|
| 153 |
+
'end': end,
|
| 154 |
+
'feature_id': position_name,
|
| 155 |
+
'feature_name': position_name,
|
| 156 |
+
'chromosome': str(chrom), # Keep chromosome number for CSPR lookup
|
| 157 |
+
'full_chromosome': full_chrom # Store full ID for sequence lookup
|
| 158 |
+
}]
|
| 159 |
+
|
| 160 |
+
# Get targets using batch processing
|
| 161 |
+
self.logger.debug(f"Searching for targets in chromosome {chrom} from {start} to {end}")
|
| 162 |
+
targets = parser.read_targets_batch(str(chrom), target_info, input_data['endonuclease'])
|
| 163 |
+
|
| 164 |
+
if targets:
|
| 165 |
+
self.logger.debug(f"Found {len(targets)} raw targets")
|
| 166 |
+
filtered_targets = []
|
| 167 |
+
guide_length = 23 # Length of guide RNA
|
| 168 |
+
|
| 169 |
+
for target in targets:
|
| 170 |
+
target_pos = int(target['position'])
|
| 171 |
+
target_end = target_pos
|
| 172 |
+
|
| 173 |
+
# Include target if:
|
| 174 |
+
# 1. Target start position is within range
|
| 175 |
+
# 2. Target end position is within or equal to end position
|
| 176 |
+
if start <= target_pos and target_end <= end + 1:
|
| 177 |
+
filtered_targets.append(target)
|
| 178 |
+
|
| 179 |
+
self.logger.debug(f"Filtered to {len(filtered_targets)} targets within range")
|
| 180 |
+
|
| 181 |
+
# Get sequence for this region
|
| 182 |
+
sequence = self._get_sequence_for_position(chrom, start, end)
|
| 183 |
+
|
| 184 |
+
# Format results
|
| 185 |
+
for target in filtered_targets:
|
| 186 |
+
result = {
|
| 187 |
+
'feature_type': 'Position',
|
| 188 |
+
'chromosome': str(chrom),
|
| 189 |
+
'feature_id': position_name,
|
| 190 |
+
'feature_name': position_name,
|
| 191 |
+
'feature_description': position_name,
|
| 192 |
+
'location': target['location'],
|
| 193 |
+
'start': start,
|
| 194 |
+
'end': end,
|
| 195 |
+
'strand': target['strand'],
|
| 196 |
+
'sequence': target['sequence'],
|
| 197 |
+
'pam': target['pam'],
|
| 198 |
+
'score': target['score'],
|
| 199 |
+
'endonuclease': target['endonuclease'],
|
| 200 |
+
'gene_sequence': sequence
|
| 201 |
+
}
|
| 202 |
+
all_results.append(result)
|
| 203 |
+
|
| 204 |
+
self.logger.debug(f"Added {len(filtered_targets)} formatted results")
|
| 205 |
+
else:
|
| 206 |
+
self.logger.warning(f"No targets found for query: {query}")
|
| 207 |
+
|
| 208 |
+
except Exception as e:
|
| 209 |
+
self.logger.error(f"Error processing query {query}: {str(e)}")
|
| 210 |
+
self.logger.error(f"Stack trace: {traceback.format_exc()}")
|
| 211 |
+
continue
|
| 212 |
+
|
| 213 |
+
self.logger.debug(f"Total results found: {len(all_results)}")
|
| 214 |
+
return all_results
|
| 215 |
+
|
| 216 |
+
except Exception as e:
|
| 217 |
+
self.logger.error(f"Error in find_targets_by_position: {str(e)}")
|
| 218 |
+
self.logger.error(f"Stack trace: {traceback.format_exc()}")
|
| 219 |
+
raise
|
| 220 |
+
|
| 221 |
+
def _get_sequence_for_position(self, chrom, start, end):
|
| 222 |
+
"""Get sequence for a given position with proper padding handling"""
|
| 223 |
+
try:
|
| 224 |
+
if not hasattr(self, 'annotation_parser') or self.annotation_parser is None:
|
| 225 |
+
self.annotation_parser = AnnotationParser(self.global_settings)
|
| 226 |
+
annotation_file = self.global_settings.get_current_annotation_file()
|
| 227 |
+
annotation_path = os.path.join(self.global_settings.get_db_path(), 'GBFF', annotation_file)
|
| 228 |
+
self.annotation_parser.set_annotation_file(annotation_path)
|
| 229 |
+
|
| 230 |
+
feature_info = {
|
| 231 |
+
'chromosome': f"NZ_CP032679.{chrom}", # Use full name 'chromosome'
|
| 232 |
+
'start': start-1, # Use full name 'start'
|
| 233 |
+
'end': end # Use full name 'end'
|
| 234 |
+
}
|
| 235 |
+
|
| 236 |
+
sequence = self.annotation_parser._get_sequence_for_gene(feature_info)
|
| 237 |
+
if sequence:
|
| 238 |
+
padding = 30
|
| 239 |
+
|
| 240 |
+
# Handle start position padding
|
| 241 |
+
if start == 1:
|
| 242 |
+
# No padding at start if starting at position 1
|
| 243 |
+
five_prime_pad = ""
|
| 244 |
+
main_sequence = sequence[:-(padding if len(sequence) > padding else 0)].upper()
|
| 245 |
+
else:
|
| 246 |
+
five_prime_pad = sequence[:padding].lower() if len(sequence) > padding else ""
|
| 247 |
+
main_sequence = sequence[padding:-padding].upper() if len(sequence) > 60 else sequence.upper()
|
| 248 |
+
|
| 249 |
+
three_prime_pad = sequence[-padding:].lower() if len(sequence) > padding else ""
|
| 250 |
+
|
| 251 |
+
return five_prime_pad + main_sequence + three_prime_pad
|
| 252 |
+
|
| 253 |
+
return None
|
| 254 |
+
|
| 255 |
+
except Exception as e:
|
| 256 |
+
self.logger.error(f"Error getting sequence for position: {str(e)}")
|
| 257 |
+
return None
|
| 258 |
|
| 259 |
def find_targets_by_sequence(self, parser, input_data):
|
| 260 |
+
"""Search for targets by sequence"""
|
| 261 |
+
try:
|
| 262 |
+
sequence = input_data['search_query'].strip().upper()
|
| 263 |
+
|
| 264 |
+
# Validate sequence length
|
| 265 |
+
if len(sequence) < 100:
|
| 266 |
+
self.logger.error("Sequence too short")
|
| 267 |
+
return []
|
| 268 |
+
|
| 269 |
+
# Get annotation file
|
| 270 |
+
annotation_file = self.global_settings.get_current_annotation_file()
|
| 271 |
+
if not annotation_file:
|
| 272 |
+
self.logger.error("No annotation file selected")
|
| 273 |
+
return []
|
| 274 |
+
|
| 275 |
+
# Initialize annotation parser
|
| 276 |
+
if not hasattr(self, 'annotation_parser') or self.annotation_parser is None:
|
| 277 |
+
self.annotation_parser = AnnotationParser(self.global_settings)
|
| 278 |
+
annotation_path = os.path.join(self.global_settings.get_db_path(), 'GBFF', annotation_file)
|
| 279 |
+
self.annotation_parser.set_annotation_file(annotation_path)
|
| 280 |
+
|
| 281 |
+
# Find sequence in genome
|
| 282 |
+
chrom_count = 0
|
| 283 |
+
for record in SeqIO.parse(self.annotation_parser.annotation_file_name, "genbank"):
|
| 284 |
+
chrom_count += 1 # Count chromosome position by caret
|
| 285 |
+
record_seq = str(record.seq).upper()
|
| 286 |
+
pos = record_seq.find(sequence)
|
| 287 |
+
|
| 288 |
+
if pos != -1:
|
| 289 |
+
# Found the sequence
|
| 290 |
+
start = pos + 1 # 1-based position
|
| 291 |
+
end = start + len(sequence) - 1
|
| 292 |
+
|
| 293 |
+
# Create position name
|
| 294 |
+
position_name = f"chrom {chrom_count}, start: {start}, end: {end}"
|
| 295 |
+
|
| 296 |
+
# Create target info
|
| 297 |
+
target_info = [{
|
| 298 |
+
'start': start,
|
| 299 |
+
'end': end,
|
| 300 |
+
'feature_id': position_name,
|
| 301 |
+
'feature_name': position_name,
|
| 302 |
+
'chromosome': str(chrom_count), # Use caret-based chromosome number
|
| 303 |
+
'full_chromosome': record.id # Store full chromosome ID
|
| 304 |
+
}]
|
| 305 |
+
|
| 306 |
+
# Get targets in this region
|
| 307 |
+
self.logger.debug(f"Found sequence in chromosome {chrom_count} from {start} to {end}")
|
| 308 |
+
targets = parser.read_targets_batch(str(chrom_count), target_info, input_data['endonuclease'])
|
| 309 |
+
|
| 310 |
+
if targets:
|
| 311 |
+
self.logger.debug(f"Found {len(targets)} raw targets")
|
| 312 |
+
filtered_targets = []
|
| 313 |
+
guide_length = 23
|
| 314 |
+
|
| 315 |
+
for target in targets:
|
| 316 |
+
target_pos = int(target['position'])
|
| 317 |
+
target_end = target_pos + guide_length
|
| 318 |
+
|
| 319 |
+
# Include target if within sequence bounds
|
| 320 |
+
if start <= target_pos and target_end <= end + 1:
|
| 321 |
+
filtered_targets.append(target)
|
| 322 |
+
|
| 323 |
+
self.logger.debug(f"Filtered to {len(filtered_targets)} targets within range")
|
| 324 |
+
|
| 325 |
+
# Get sequence with padding
|
| 326 |
+
sequence_with_padding = self._get_sequence_for_position(chrom_count, start, end)
|
| 327 |
+
|
| 328 |
+
# Format results
|
| 329 |
+
all_results = []
|
| 330 |
+
for target in filtered_targets:
|
| 331 |
+
result = {
|
| 332 |
+
'feature_type': 'Position',
|
| 333 |
+
'chromosome': str(chrom_count),
|
| 334 |
+
'feature_id': position_name,
|
| 335 |
+
'feature_name': position_name,
|
| 336 |
+
'feature_description': f"Sequence match at {position_name}",
|
| 337 |
+
'location': target['location'],
|
| 338 |
+
'start': start,
|
| 339 |
+
'end': end,
|
| 340 |
+
'strand': target['strand'],
|
| 341 |
+
'sequence': target['sequence'],
|
| 342 |
+
'pam': target['pam'],
|
| 343 |
+
'score': target['score'],
|
| 344 |
+
'endonuclease': target['endonuclease'],
|
| 345 |
+
'gene_sequence': sequence_with_padding
|
| 346 |
+
}
|
| 347 |
+
all_results.append(result)
|
| 348 |
+
|
| 349 |
+
return all_results
|
| 350 |
+
|
| 351 |
+
self.logger.warning("Sequence not found in genome")
|
| 352 |
return []
|
| 353 |
+
|
| 354 |
+
except Exception as e:
|
| 355 |
+
self.logger.error(f"Error in find_targets_by_sequence: {str(e)}")
|
| 356 |
+
self.logger.error(f"Stack trace: {traceback.format_exc()}")
|
| 357 |
+
raise
|
| 358 |
|
| 359 |
def _format_results(self, targets):
|
| 360 |
formatted_results = []
|
|
@@ -0,0 +1,239 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from models.CSPRparser import CSPRparser
|
| 2 |
+
from models.HomeWindowModel import HomeWindowModel
|
| 3 |
+
import os
|
| 4 |
+
import re
|
| 5 |
+
import traceback
|
| 6 |
+
|
| 7 |
+
class GenerateLibraryModel(HomeWindowModel):
|
| 8 |
+
def __init__(self, global_settings):
|
| 9 |
+
super().__init__(global_settings)
|
| 10 |
+
self.logger = global_settings.logger
|
| 11 |
+
self.parser = None
|
| 12 |
+
self.targets_data = {}
|
| 13 |
+
self._deleted_targets = {}
|
| 14 |
+
|
| 15 |
+
def initialize_parser(self, cspr_file):
|
| 16 |
+
"""Initialize CSPR parser"""
|
| 17 |
+
self.parser = CSPRparser(cspr_file, self.global_settings.get_casper_info_path())
|
| 18 |
+
|
| 19 |
+
def generate_library(self, selected_targets, settings):
|
| 20 |
+
"""Generate library with given settings"""
|
| 21 |
+
try:
|
| 22 |
+
self.logger.debug(f"Generating library with settings: {settings}")
|
| 23 |
+
|
| 24 |
+
# Process targets based on settings
|
| 25 |
+
processed_targets = self._process_targets(
|
| 26 |
+
selected_targets,
|
| 27 |
+
settings['min_score'],
|
| 28 |
+
settings['five_prime_seq'],
|
| 29 |
+
settings['target_range_start'],
|
| 30 |
+
settings['target_range_end']
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
# Generate output for each target
|
| 34 |
+
output_data = self._generate_output(
|
| 35 |
+
processed_targets,
|
| 36 |
+
settings['guides_per_gene'],
|
| 37 |
+
settings['space_between_guides']
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
self.logger.debug(f"Output data: {output_data}")
|
| 41 |
+
|
| 42 |
+
# Write output to file
|
| 43 |
+
self._write_output(output_data, settings)
|
| 44 |
+
|
| 45 |
+
return True
|
| 46 |
+
|
| 47 |
+
except Exception as e:
|
| 48 |
+
self.logger.error(f"Error generating library: {str(e)}")
|
| 49 |
+
self.logger.error(traceback.format_exc())
|
| 50 |
+
raise
|
| 51 |
+
|
| 52 |
+
def _process_targets(self, targets, min_score, five_prime_seq, start_range, end_range):
|
| 53 |
+
"""Process and filter targets based on criteria"""
|
| 54 |
+
processed = {}
|
| 55 |
+
self._deleted_targets = {} # Store deleted targets for modify parameters option
|
| 56 |
+
|
| 57 |
+
self.logger.debug(f"Processing {len(targets)} targets with min_score={min_score}")
|
| 58 |
+
|
| 59 |
+
for target in targets:
|
| 60 |
+
# Use feature_name (gene name) as key instead of feature_id
|
| 61 |
+
gene_name = target.get('feature_name', target.get('feature_id'))
|
| 62 |
+
|
| 63 |
+
if gene_name not in processed:
|
| 64 |
+
processed[gene_name] = []
|
| 65 |
+
self._deleted_targets[gene_name] = []
|
| 66 |
+
|
| 67 |
+
# Create a copy of the target data
|
| 68 |
+
target_data = target.copy()
|
| 69 |
+
|
| 70 |
+
# Check if target passes filters
|
| 71 |
+
if self._passes_filters(target_data, min_score, five_prime_seq, start_range, end_range):
|
| 72 |
+
processed[gene_name].append(target_data)
|
| 73 |
+
else:
|
| 74 |
+
self._deleted_targets[gene_name].append(target_data)
|
| 75 |
+
|
| 76 |
+
# Sort targets for each gene
|
| 77 |
+
for gene in processed:
|
| 78 |
+
# First sort by score (ascending)
|
| 79 |
+
processed[gene].sort(key=lambda x: float(x['score']))
|
| 80 |
+
|
| 81 |
+
# Then sort by position (ascending)
|
| 82 |
+
processed[gene].sort(key=lambda x: abs(int(x['position'])))
|
| 83 |
+
|
| 84 |
+
# Reverse list if gene is on negative strand
|
| 85 |
+
if processed[gene] and processed[gene][0].get('strand', '+') == '-':
|
| 86 |
+
processed[gene].reverse()
|
| 87 |
+
|
| 88 |
+
return processed
|
| 89 |
+
|
| 90 |
+
def _passes_filters(self, target, min_score, five_prime_seq, start_range, end_range):
|
| 91 |
+
"""Check if target passes all filters"""
|
| 92 |
+
try:
|
| 93 |
+
# Score filter - convert score to float and compare
|
| 94 |
+
target_score = float(target.get('score', 0))
|
| 95 |
+
if target_score < min_score:
|
| 96 |
+
self.logger.debug(f"Target failed score filter: {target_score} < {min_score}")
|
| 97 |
+
return False
|
| 98 |
+
|
| 99 |
+
# Poly-T filter
|
| 100 |
+
if re.search("T{5,10}", target['sequence']):
|
| 101 |
+
self.logger.debug(f"Target failed poly-T filter: {target['sequence']}")
|
| 102 |
+
return False
|
| 103 |
+
|
| 104 |
+
# 5' sequence filter
|
| 105 |
+
if five_prime_seq and not target['sequence'].startswith(five_prime_seq.upper()):
|
| 106 |
+
self.logger.debug(f"Target failed 5' sequence filter")
|
| 107 |
+
return False
|
| 108 |
+
|
| 109 |
+
# Range filter
|
| 110 |
+
if start_range != 0 or end_range != 100:
|
| 111 |
+
position_ratio = self._calculate_position_ratio(target)
|
| 112 |
+
if not (start_range/100 <= position_ratio <= end_range/100):
|
| 113 |
+
self.logger.debug(f"Target failed range filter: {position_ratio}")
|
| 114 |
+
return False
|
| 115 |
+
|
| 116 |
+
return True
|
| 117 |
+
|
| 118 |
+
except Exception as e:
|
| 119 |
+
self.logger.error(f"Error in _passes_filters: {str(e)}")
|
| 120 |
+
self.logger.error(f"Target data: {target}")
|
| 121 |
+
return False
|
| 122 |
+
|
| 123 |
+
def _calculate_position_ratio(self, target):
|
| 124 |
+
"""Calculate relative position ratio in gene"""
|
| 125 |
+
try:
|
| 126 |
+
# Get gene length from feature info
|
| 127 |
+
gene_length = abs(int(target.get('end', 0)) - int(target.get('start', 0)))
|
| 128 |
+
if gene_length == 0:
|
| 129 |
+
return 0
|
| 130 |
+
|
| 131 |
+
# Calculate position relative to gene start
|
| 132 |
+
target_pos = abs(int(target['position']))
|
| 133 |
+
gene_start = int(target.get('start', 0))
|
| 134 |
+
|
| 135 |
+
# If gene is on reverse strand, flip the position calculation
|
| 136 |
+
if target.get('strand', '+') == '-':
|
| 137 |
+
ratio = (target.get('end', 0) - target_pos) / gene_length
|
| 138 |
+
else:
|
| 139 |
+
ratio = (target_pos - gene_start) / gene_length
|
| 140 |
+
|
| 141 |
+
self.logger.debug(f"Position ratio: {ratio} (pos={target_pos}, start={gene_start}, len={gene_length}, strand={target.get('strand', '+')}")
|
| 142 |
+
return ratio
|
| 143 |
+
|
| 144 |
+
except Exception as e:
|
| 145 |
+
self.logger.error(f"Error calculating position ratio: {str(e)}")
|
| 146 |
+
return 0
|
| 147 |
+
|
| 148 |
+
def _generate_output(self, processed_targets, guides_per_gene, space_between):
|
| 149 |
+
output = {}
|
| 150 |
+
|
| 151 |
+
for gene_id, targets in processed_targets.items():
|
| 152 |
+
output[gene_id] = []
|
| 153 |
+
i = 0
|
| 154 |
+
vec_index = 0
|
| 155 |
+
prev_target = None
|
| 156 |
+
|
| 157 |
+
while i < guides_per_gene:
|
| 158 |
+
if len(targets) == 0 or vec_index >= len(targets):
|
| 159 |
+
break
|
| 160 |
+
|
| 161 |
+
current = targets[vec_index]
|
| 162 |
+
|
| 163 |
+
# Check spacing from previous target
|
| 164 |
+
if prev_target is None or abs(int(current['position']) - int(prev_target['position'])) >= space_between:
|
| 165 |
+
# If current target has better score than previous
|
| 166 |
+
if (prev_target and float(current['score']) > float(prev_target['score'])):
|
| 167 |
+
output[gene_id].pop()
|
| 168 |
+
output[gene_id].append(current)
|
| 169 |
+
else:
|
| 170 |
+
output[gene_id].append(current)
|
| 171 |
+
prev_target = current
|
| 172 |
+
i += 1
|
| 173 |
+
|
| 174 |
+
vec_index += 1
|
| 175 |
+
if vec_index >= len(targets):
|
| 176 |
+
break
|
| 177 |
+
|
| 178 |
+
# Add deleted targets if needed
|
| 179 |
+
if len(output[gene_id]) < guides_per_gene:
|
| 180 |
+
deleted_sorted = sorted(
|
| 181 |
+
self._deleted_targets.get(gene_id, []),
|
| 182 |
+
key=lambda x: (float(x['score']), abs(int(x['position'])))
|
| 183 |
+
)
|
| 184 |
+
|
| 185 |
+
for deleted_target in deleted_sorted:
|
| 186 |
+
if len(output[gene_id]) >= guides_per_gene:
|
| 187 |
+
break
|
| 188 |
+
deleted_target['modified'] = True
|
| 189 |
+
output[gene_id].append(deleted_target)
|
| 190 |
+
|
| 191 |
+
return output
|
| 192 |
+
|
| 193 |
+
def _write_output(self, output_data, settings):
|
| 194 |
+
"""Write library to output file"""
|
| 195 |
+
output_file = settings['output_file']
|
| 196 |
+
if not output_file.endswith('.csv'):
|
| 197 |
+
output_file += '.csv'
|
| 198 |
+
|
| 199 |
+
with open(output_file, 'w') as f:
|
| 200 |
+
# Write header
|
| 201 |
+
headers = ['Gene Name', 'Sequence', 'On-Target Score']
|
| 202 |
+
if settings.get('find_off_targets'):
|
| 203 |
+
headers.append('Off-Target Score')
|
| 204 |
+
headers.extend(['Location', 'PAM', 'Strand'])
|
| 205 |
+
f.write(','.join(headers) + '\n')
|
| 206 |
+
|
| 207 |
+
# Write data
|
| 208 |
+
for gene_id, targets in output_data.items():
|
| 209 |
+
if not targets: # Skip genes with no targets
|
| 210 |
+
continue
|
| 211 |
+
|
| 212 |
+
for i, target in enumerate(targets, 1):
|
| 213 |
+
# Use feature_name (gene name) instead of feature_id
|
| 214 |
+
gene_name = target.get('feature_name', gene_id)
|
| 215 |
+
|
| 216 |
+
# Format gene name with index
|
| 217 |
+
tag_id = f"{gene_name}-{i}"
|
| 218 |
+
tag_id = tag_id.replace(',', '') # Remove any commas
|
| 219 |
+
|
| 220 |
+
# If target was modified by parameters, add asterisks
|
| 221 |
+
if settings.get('modify_params') and target.get('modified'):
|
| 222 |
+
tag_id = "**" + tag_id
|
| 223 |
+
|
| 224 |
+
row = [
|
| 225 |
+
tag_id,
|
| 226 |
+
target['sequence'],
|
| 227 |
+
str(target['score'])
|
| 228 |
+
]
|
| 229 |
+
|
| 230 |
+
if settings.get('find_off_targets'):
|
| 231 |
+
row.append(str(target.get('off_target_score', '')))
|
| 232 |
+
|
| 233 |
+
row.extend([
|
| 234 |
+
str(abs(int(target['position']))),
|
| 235 |
+
target['pam'],
|
| 236 |
+
target['strand'][0] # Just take first character of strand
|
| 237 |
+
])
|
| 238 |
+
|
| 239 |
+
f.write(','.join(row) + '\n')
|
|
@@ -8,20 +8,19 @@ from PyQt6.QtCore import QSettings, QObject, pyqtSignal
|
|
| 8 |
from PyQt6.QtGui import QPalette, QColor
|
| 9 |
from PyQt6.QtWidgets import QApplication
|
| 10 |
|
| 11 |
-
from models.DatabaseManager import DatabaseManager
|
| 12 |
from models.ConfigManager import ConfigManager
|
| 13 |
|
| 14 |
class GlobalSettings(QObject):
|
| 15 |
-
|
| 16 |
-
first_time_startup = pyqtSignal() # New signal
|
| 17 |
endonuclease_updated = pyqtSignal()
|
| 18 |
-
annotation_file_changed = pyqtSignal(str)
|
|
|
|
| 19 |
|
| 20 |
def __init__(self, app_dir_path):
|
| 21 |
super().__init__()
|
| 22 |
|
| 23 |
self.app_dir_path = app_dir_path
|
| 24 |
-
|
| 25 |
self.logger = self._setup_logging()
|
| 26 |
|
| 27 |
self.config_manager = ConfigManager(app_dir_path=self.app_dir_path, logger=self.logger)
|
|
@@ -29,10 +28,13 @@ class GlobalSettings(QObject):
|
|
| 29 |
|
| 30 |
self.is_first_time_startup = self.config_manager.get_env_value('FIRST_TIME_START', 'TRUE').upper() == 'TRUE'
|
| 31 |
|
| 32 |
-
self._initialize_directories()
|
| 33 |
|
| 34 |
self.db_manager = DatabaseManager(self.logger, self.config_manager)
|
| 35 |
-
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
self.CSPR_DB = self.db_manager.get_db_path()
|
| 38 |
self.algorithms = self.config_manager.get_config_value('algorithms', ["Azimuth 2.0"])
|
|
@@ -45,19 +47,65 @@ class GlobalSettings(QObject):
|
|
| 45 |
self.initialize_palettes()
|
| 46 |
|
| 47 |
self.main_window = None
|
| 48 |
-
|
| 49 |
self._current_annotation_file = None
|
| 50 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
def _initialize_directories(self):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
self.src_dir_path = os.path.join(self.app_dir_path, 'src')
|
| 53 |
-
self.ui_dir_path = os.path.join(self.src_dir_path,
|
| 54 |
-
self.controllers_dir_path = os.path.join(self.src_dir_path,
|
| 55 |
-
self.assets_dir_path = os.path.join(self.app_dir_path, self.config_manager.get_config_value('paths.assets'))
|
| 56 |
self.models_dir_path = os.path.join(self.src_dir_path, 'models')
|
| 57 |
self.views_dir_path = os.path.join(self.src_dir_path, 'views')
|
| 58 |
self.utils_dir_path = os.path.join(self.src_dir_path, 'utils')
|
| 59 |
self.SeqFinder_dir_path = os.path.join(self.src_dir_path, 'SeqFinder')
|
| 60 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
|
| 62 |
def _setup_logging(self):
|
| 63 |
logger = logging.getLogger(__name__)
|
|
@@ -81,24 +129,6 @@ class GlobalSettings(QObject):
|
|
| 81 |
|
| 82 |
return logger
|
| 83 |
|
| 84 |
-
def get_db_path(self):
|
| 85 |
-
return self.db_manager.get_db_path()
|
| 86 |
-
|
| 87 |
-
def validate_db_path(self, path):
|
| 88 |
-
return self.db_manager.validate_db_path(path)
|
| 89 |
-
|
| 90 |
-
def save_db_path(self, path):
|
| 91 |
-
return self.db_manager.save_db_path(path)
|
| 92 |
-
|
| 93 |
-
def _on_db_state_updated(self, is_valid, message, cspr_files):
|
| 94 |
-
self.db_state_updated.emit(is_valid, message, cspr_files)
|
| 95 |
-
|
| 96 |
-
def ensure_db_path_exists(self):
|
| 97 |
-
self.db_manager.ensure_db_path_exists()
|
| 98 |
-
|
| 99 |
-
def adjust_path_for_os(self, path):
|
| 100 |
-
return self.db_manager.adjust_path_for_os(path)
|
| 101 |
-
|
| 102 |
def get_app_dir_path(self):
|
| 103 |
return self.app_dir_path
|
| 104 |
|
|
@@ -123,6 +153,9 @@ class GlobalSettings(QObject):
|
|
| 123 |
def get_casper_info_path(self):
|
| 124 |
return self.casper_info_path
|
| 125 |
|
|
|
|
|
|
|
|
|
|
| 126 |
def get_theme(self):
|
| 127 |
return self.theme
|
| 128 |
|
|
@@ -180,15 +213,77 @@ class GlobalSettings(QObject):
|
|
| 180 |
|
| 181 |
@lru_cache(maxsize=None)
|
| 182 |
def _get_window_class(self, window_name):
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
|
| 188 |
def _create_window(self, window_name):
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 192 |
|
| 193 |
def get_startup_window(self):
|
| 194 |
if not hasattr(self, '_startup_window'):
|
|
@@ -239,9 +334,6 @@ class GlobalSettings(QObject):
|
|
| 239 |
def set_main_window(self, main_window):
|
| 240 |
self.main_window = main_window
|
| 241 |
|
| 242 |
-
def update_db_state(self):
|
| 243 |
-
self.db_manager.check_db_state()
|
| 244 |
-
|
| 245 |
def _on_env_file_created(self):
|
| 246 |
self.logger.info("GlobalSettings: _on_env_file_created")
|
| 247 |
self.is_first_time_startup = True
|
|
@@ -290,5 +382,48 @@ class GlobalSettings(QObject):
|
|
| 290 |
from controllers.ScoringOptionsController import ScoringOptionsController
|
| 291 |
return ScoringOptionsController(self, view_targets_controller)
|
| 292 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 293 |
# Global instance
|
| 294 |
global_settings = None
|
|
|
|
| 8 |
from PyQt6.QtGui import QPalette, QColor
|
| 9 |
from PyQt6.QtWidgets import QApplication
|
| 10 |
|
| 11 |
+
from models.DatabaseManager import DatabaseManager, FileChangeType
|
| 12 |
from models.ConfigManager import ConfigManager
|
| 13 |
|
| 14 |
class GlobalSettings(QObject):
|
| 15 |
+
first_time_startup = pyqtSignal()
|
|
|
|
| 16 |
endonuclease_updated = pyqtSignal()
|
| 17 |
+
annotation_file_changed = pyqtSignal(str)
|
| 18 |
+
theme_changed = pyqtSignal(str)
|
| 19 |
|
| 20 |
def __init__(self, app_dir_path):
|
| 21 |
super().__init__()
|
| 22 |
|
| 23 |
self.app_dir_path = app_dir_path
|
|
|
|
| 24 |
self.logger = self._setup_logging()
|
| 25 |
|
| 26 |
self.config_manager = ConfigManager(app_dir_path=self.app_dir_path, logger=self.logger)
|
|
|
|
| 28 |
|
| 29 |
self.is_first_time_startup = self.config_manager.get_env_value('FIRST_TIME_START', 'TRUE').upper() == 'TRUE'
|
| 30 |
|
| 31 |
+
self._initialize_directories()
|
| 32 |
|
| 33 |
self.db_manager = DatabaseManager(self.logger, self.config_manager)
|
| 34 |
+
|
| 35 |
+
self.db_manager.db_files_changed.connect(self._on_db_files_changed)
|
| 36 |
+
self.db_manager.db_validation_changed.connect(self._on_db_validation_changed)
|
| 37 |
+
self.db_manager.db_state_changed.connect(self._on_db_state_changed)
|
| 38 |
|
| 39 |
self.CSPR_DB = self.db_manager.get_db_path()
|
| 40 |
self.algorithms = self.config_manager.get_config_value('algorithms', ["Azimuth 2.0"])
|
|
|
|
| 47 |
self.initialize_palettes()
|
| 48 |
|
| 49 |
self.main_window = None
|
|
|
|
| 50 |
self._current_annotation_file = None
|
| 51 |
|
| 52 |
+
def _on_db_files_changed(self, changes):
|
| 53 |
+
"""Handle database file changes"""
|
| 54 |
+
self.logger.debug(f"Database files changed: {changes}")
|
| 55 |
+
# Components should connect directly to db_manager signals
|
| 56 |
+
# This method is for global-level handling if needed
|
| 57 |
+
|
| 58 |
+
def _on_db_validation_changed(self, is_valid, message):
|
| 59 |
+
"""Handle database validation state changes"""
|
| 60 |
+
self.logger.debug(f"Database validation changed - Valid: {is_valid}, Message: {message}")
|
| 61 |
+
# Handle any global-level validation state changes
|
| 62 |
+
|
| 63 |
+
def _on_db_state_changed(self, is_valid, message, changes):
|
| 64 |
+
"""Handle combined database state changes"""
|
| 65 |
+
self.logger.debug(f"Database state changed - Valid: {is_valid}, Message: {message}, Changes: {changes}")
|
| 66 |
+
# Handle any global-level state changes
|
| 67 |
+
|
| 68 |
+
def get_db_path(self):
|
| 69 |
+
"""Get the current database path"""
|
| 70 |
+
return self.db_manager.get_db_path()
|
| 71 |
+
|
| 72 |
+
def validate_db_path(self, path):
|
| 73 |
+
"""Validate the given database path"""
|
| 74 |
+
return self.db_manager.validate_db_path(path)
|
| 75 |
+
|
| 76 |
+
def save_db_path(self, path):
|
| 77 |
+
"""Save the database path"""
|
| 78 |
+
return self.db_manager.save_db_path(path)
|
| 79 |
+
|
| 80 |
+
def ensure_db_path_exists(self):
|
| 81 |
+
"""Ensure the database path exists"""
|
| 82 |
+
self.db_manager.ensure_db_path_exists()
|
| 83 |
+
|
| 84 |
+
def update_db_state(self):
|
| 85 |
+
"""Check and update the database state"""
|
| 86 |
+
self.db_manager.check_db_state()
|
| 87 |
+
|
| 88 |
def _initialize_directories(self):
|
| 89 |
+
"""Initialize application directories"""
|
| 90 |
+
# app_dir_path is already set in __init__
|
| 91 |
+
|
| 92 |
+
# Set up source directory paths
|
| 93 |
self.src_dir_path = os.path.join(self.app_dir_path, 'src')
|
| 94 |
+
self.ui_dir_path = os.path.join(self.src_dir_path, 'ui')
|
| 95 |
+
self.controllers_dir_path = os.path.join(self.src_dir_path, 'controllers')
|
|
|
|
| 96 |
self.models_dir_path = os.path.join(self.src_dir_path, 'models')
|
| 97 |
self.views_dir_path = os.path.join(self.src_dir_path, 'views')
|
| 98 |
self.utils_dir_path = os.path.join(self.src_dir_path, 'utils')
|
| 99 |
self.SeqFinder_dir_path = os.path.join(self.src_dir_path, 'SeqFinder')
|
| 100 |
+
|
| 101 |
+
# Set up other resource paths
|
| 102 |
+
self.assets_dir_path = os.path.join(self.app_dir_path, 'assets')
|
| 103 |
+
self.config_dir_path = os.path.join(self.app_dir_path, 'config')
|
| 104 |
+
self.casper_info_path = os.path.join(self.config_dir_path, 'CASPERinfo')
|
| 105 |
+
self.off_target_dir_path = os.path.join(self.models_dir_path, 'OffTarget')
|
| 106 |
+
|
| 107 |
+
# Ensure critical directories exist
|
| 108 |
+
os.makedirs(self.config_dir_path, exist_ok=True)
|
| 109 |
|
| 110 |
def _setup_logging(self):
|
| 111 |
logger = logging.getLogger(__name__)
|
|
|
|
| 129 |
|
| 130 |
return logger
|
| 131 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
def get_app_dir_path(self):
|
| 133 |
return self.app_dir_path
|
| 134 |
|
|
|
|
| 153 |
def get_casper_info_path(self):
|
| 154 |
return self.casper_info_path
|
| 155 |
|
| 156 |
+
def get_off_target_dir_path(self):
|
| 157 |
+
return self.off_target_dir_path
|
| 158 |
+
|
| 159 |
def get_theme(self):
|
| 160 |
return self.theme
|
| 161 |
|
|
|
|
| 213 |
|
| 214 |
@lru_cache(maxsize=None)
|
| 215 |
def _get_window_class(self, window_name):
|
| 216 |
+
"""Get the controller class with better error handling and dynamic imports"""
|
| 217 |
+
try:
|
| 218 |
+
# Get the application root directory
|
| 219 |
+
if hasattr(sys, 'frozen'):
|
| 220 |
+
root_dir = os.path.join(os.path.dirname(sys.executable), 'src')
|
| 221 |
+
if platform.system() == 'Darwin': # macOS
|
| 222 |
+
root_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(sys.executable))),
|
| 223 |
+
'Contents', 'Resources', 'src')
|
| 224 |
+
else:
|
| 225 |
+
root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 226 |
+
|
| 227 |
+
# Add root directory to Python path if not already there
|
| 228 |
+
if root_dir not in sys.path:
|
| 229 |
+
sys.path.insert(0, root_dir)
|
| 230 |
+
|
| 231 |
+
# Import model (optional)
|
| 232 |
+
try:
|
| 233 |
+
model_name = f"{window_name}Model"
|
| 234 |
+
model_file = os.path.join(root_dir, 'models', f"{model_name}.py")
|
| 235 |
+
|
| 236 |
+
if os.path.exists(model_file):
|
| 237 |
+
spec = importlib.util.spec_from_file_location(
|
| 238 |
+
f"models.{model_name}",
|
| 239 |
+
model_file
|
| 240 |
+
)
|
| 241 |
+
model_module = importlib.util.module_from_spec(spec)
|
| 242 |
+
spec.loader.exec_module(model_module)
|
| 243 |
+
sys.modules[f"models.{model_name}"] = model_module
|
| 244 |
+
self.logger.debug(f"Successfully imported model from {model_file}")
|
| 245 |
+
except Exception as e:
|
| 246 |
+
self.logger.warning(f"Could not find model for {window_name}: {str(e)}")
|
| 247 |
+
|
| 248 |
+
# Import controller (required)
|
| 249 |
+
controller_name = f"{window_name}Controller"
|
| 250 |
+
controller_file = os.path.join(root_dir, 'controllers', f"{controller_name}.py")
|
| 251 |
+
|
| 252 |
+
if not os.path.exists(controller_file):
|
| 253 |
+
raise ImportError(f"Controller file not found: {controller_file}")
|
| 254 |
+
|
| 255 |
+
spec = importlib.util.spec_from_file_location(
|
| 256 |
+
f"controllers.{controller_name}",
|
| 257 |
+
controller_file
|
| 258 |
+
)
|
| 259 |
+
controller_module = importlib.util.module_from_spec(spec)
|
| 260 |
+
spec.loader.exec_module(controller_module)
|
| 261 |
+
sys.modules[f"controllers.{controller_name}"] = controller_module
|
| 262 |
+
|
| 263 |
+
class_name = f"{window_name}Controller"
|
| 264 |
+
if not hasattr(controller_module, class_name):
|
| 265 |
+
raise AttributeError(f"Controller module does not contain class {class_name}")
|
| 266 |
+
|
| 267 |
+
self.logger.debug(f"Successfully imported controller from {controller_file}")
|
| 268 |
+
return getattr(controller_module, class_name)
|
| 269 |
+
|
| 270 |
+
except Exception as e:
|
| 271 |
+
self.logger.error(f"Failed to load controller {window_name}: {str(e)}")
|
| 272 |
+
raise ImportError(f"Could not load controller for {window_name}") from e
|
| 273 |
|
| 274 |
def _create_window(self, window_name):
|
| 275 |
+
"""Create a window instance with dynamic module loading"""
|
| 276 |
+
try:
|
| 277 |
+
WindowClass = self._get_window_class(window_name)
|
| 278 |
+
controller = WindowClass(self)
|
| 279 |
+
|
| 280 |
+
# Store the reference to prevent garbage collection
|
| 281 |
+
setattr(self, f'_current_{window_name.lower()}_window', controller)
|
| 282 |
+
|
| 283 |
+
return controller
|
| 284 |
+
except Exception as e:
|
| 285 |
+
self.logger.error(f"Error creating window {window_name}: {str(e)}")
|
| 286 |
+
raise
|
| 287 |
|
| 288 |
def get_startup_window(self):
|
| 289 |
if not hasattr(self, '_startup_window'):
|
|
|
|
| 334 |
def set_main_window(self, main_window):
|
| 335 |
self.main_window = main_window
|
| 336 |
|
|
|
|
|
|
|
|
|
|
| 337 |
def _on_env_file_created(self):
|
| 338 |
self.logger.info("GlobalSettings: _on_env_file_created")
|
| 339 |
self.is_first_time_startup = True
|
|
|
|
| 382 |
from controllers.ScoringOptionsController import ScoringOptionsController
|
| 383 |
return ScoringOptionsController(self, view_targets_controller)
|
| 384 |
|
| 385 |
+
def get_stylesheet(self):
|
| 386 |
+
"""Return the base stylesheet for the application"""
|
| 387 |
+
# Implement this method to return a base stylesheet
|
| 388 |
+
pass
|
| 389 |
+
|
| 390 |
+
def get_groupbox_style(self):
|
| 391 |
+
"""Return the style for group boxes"""
|
| 392 |
+
# Implement this method to return the group box style
|
| 393 |
+
pass
|
| 394 |
+
|
| 395 |
+
def get_dark_stylesheet(self):
|
| 396 |
+
"""Return the dark theme stylesheet"""
|
| 397 |
+
# Implement this method to return the dark theme stylesheet
|
| 398 |
+
pass
|
| 399 |
+
|
| 400 |
+
def get_light_stylesheet(self):
|
| 401 |
+
"""Return the light theme stylesheet"""
|
| 402 |
+
# Implement this method to return the light theme stylesheet
|
| 403 |
+
pass
|
| 404 |
+
|
| 405 |
+
def set_theme(self, theme):
|
| 406 |
+
"""Set the current theme and notify listeners"""
|
| 407 |
+
self.theme = theme
|
| 408 |
+
self.theme_changed.emit(theme)
|
| 409 |
+
|
| 410 |
+
def get_theme(self):
|
| 411 |
+
"""Get the current theme"""
|
| 412 |
+
return self.theme
|
| 413 |
+
|
| 414 |
+
def get_cotargeting_window(self, view_targets_controller=None):
|
| 415 |
+
"""Create and return CoTargetingController instance"""
|
| 416 |
+
if not hasattr(self, '_cotargeting_controller'):
|
| 417 |
+
from controllers.CoTargetingController import CoTargetingController
|
| 418 |
+
self._cotargeting_controller = CoTargetingController(self, view_targets_controller)
|
| 419 |
+
return self._cotargeting_controller
|
| 420 |
+
|
| 421 |
+
def get_export_selected_grnas_window(self):
|
| 422 |
+
"""Get or create ExportSelectedgRNAs window"""
|
| 423 |
+
if not hasattr(self, '_export_selected_grnas_controller'):
|
| 424 |
+
from controllers.ExportSelectedgRNAsController import ExportSelectedgRNAsController
|
| 425 |
+
self._export_selected_grnas_controller = ExportSelectedgRNAsController(self)
|
| 426 |
+
return self._export_selected_grnas_controller
|
| 427 |
+
|
| 428 |
# Global instance
|
| 429 |
global_settings = None
|
|
@@ -1,38 +1,66 @@
|
|
| 1 |
import os
|
| 2 |
import glob
|
| 3 |
-
from typing import Dict, List
|
| 4 |
from utils.ui import show_error
|
|
|
|
| 5 |
|
| 6 |
class HomeWindowModel:
|
| 7 |
def __init__(self, global_settings):
|
| 8 |
self.global_settings = global_settings
|
| 9 |
self.logger = global_settings.get_logger()
|
| 10 |
-
self.data = {
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
self.endo_name = ""
|
| 16 |
-
self.org = ""
|
| 17 |
-
self.genlib_list = []
|
| 18 |
-
self.results_list = []
|
| 19 |
self.load_data()
|
| 20 |
|
| 21 |
def load_data(self) -> None:
|
|
|
|
| 22 |
try:
|
| 23 |
self.load_organisms_and_endonuclease()
|
| 24 |
self.load_annotation_files()
|
| 25 |
-
|
| 26 |
except Exception as e:
|
| 27 |
self.logger.error(f"Error loading data: {str(e)}")
|
| 28 |
show_error(self.global_settings, "Error Loading Data", str(e))
|
| 29 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
def load_organisms_and_endonuclease(self) -> None:
|
|
|
|
| 31 |
try:
|
|
|
|
| 32 |
self.data["organism_to_files"] = {}
|
| 33 |
self.data["organism_to_endonuclease"] = {}
|
|
|
|
| 34 |
cspr_files = glob.glob(os.path.join(self.global_settings.get_db_path(), "*.cspr"))
|
| 35 |
-
|
| 36 |
for file in cspr_files:
|
| 37 |
file_name = os.path.basename(file)
|
| 38 |
file_name_no_ext = file_name[:-5]
|
|
@@ -41,38 +69,58 @@ class HomeWindowModel:
|
|
| 41 |
with open(file, 'r') as f:
|
| 42 |
organism = f.readline().strip().replace("GENOME: ", '')
|
| 43 |
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
-
|
|
|
|
|
|
|
|
|
|
| 50 |
self.data["organism_to_endonuclease"][organism].append(endonuclease)
|
| 51 |
-
else:
|
| 52 |
-
self.data["organism_to_endonuclease"][organism] = [endonuclease]
|
| 53 |
|
| 54 |
-
self.logger.debug("
|
|
|
|
| 55 |
except Exception as e:
|
| 56 |
-
self.logger.error(f"Error
|
|
|
|
| 57 |
|
| 58 |
def load_annotation_files(self) -> None:
|
|
|
|
| 59 |
try:
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
except Exception as e:
|
| 66 |
-
self.logger.error(f"Error
|
|
|
|
| 67 |
|
| 68 |
def get_organism_to_files(self) -> Dict[str, Dict[str, List[str]]]:
|
|
|
|
| 69 |
return self.data['organism_to_files']
|
| 70 |
|
| 71 |
def get_organism_to_endonuclease(self) -> Dict[str, List[str]]:
|
|
|
|
| 72 |
return self.data.get("organism_to_endonuclease", {})
|
| 73 |
|
| 74 |
def get_annotation_files(self) -> List[str]:
|
| 75 |
-
|
|
|
|
| 76 |
|
| 77 |
def find_targets(self, input_data: dict) -> None:
|
| 78 |
pass
|
|
|
|
| 1 |
import os
|
| 2 |
import glob
|
| 3 |
+
from typing import Dict, List, Set
|
| 4 |
from utils.ui import show_error
|
| 5 |
+
from models.DatabaseManager import FileChangeType
|
| 6 |
|
| 7 |
class HomeWindowModel:
|
| 8 |
def __init__(self, global_settings):
|
| 9 |
self.global_settings = global_settings
|
| 10 |
self.logger = global_settings.get_logger()
|
| 11 |
+
self.data = {
|
| 12 |
+
'organism_to_files': {},
|
| 13 |
+
'organism_to_endonuclease': {},
|
| 14 |
+
'annotation_files': set() # Using set for efficient updates
|
| 15 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
self.load_data()
|
| 17 |
|
| 18 |
def load_data(self) -> None:
|
| 19 |
+
"""Load all required data"""
|
| 20 |
try:
|
| 21 |
self.load_organisms_and_endonuclease()
|
| 22 |
self.load_annotation_files()
|
| 23 |
+
self.logger.debug("Successfully loaded all home window data")
|
| 24 |
except Exception as e:
|
| 25 |
self.logger.error(f"Error loading data: {str(e)}")
|
| 26 |
show_error(self.global_settings, "Error Loading Data", str(e))
|
| 27 |
|
| 28 |
+
def update_for_file_changes(self, changes: Dict[FileChangeType, List[str]]) -> None:
|
| 29 |
+
"""Update model data based on file changes"""
|
| 30 |
+
try:
|
| 31 |
+
needs_organism_reload = False
|
| 32 |
+
needs_annotation_reload = False
|
| 33 |
+
|
| 34 |
+
# Check what types of files changed
|
| 35 |
+
if (FileChangeType.CSPR_ADDED in changes or
|
| 36 |
+
FileChangeType.CSPR_REMOVED in changes):
|
| 37 |
+
needs_organism_reload = True
|
| 38 |
+
|
| 39 |
+
if (FileChangeType.GBFF_ADDED in changes or
|
| 40 |
+
FileChangeType.GBFF_REMOVED in changes):
|
| 41 |
+
needs_annotation_reload = True
|
| 42 |
+
|
| 43 |
+
# Reload only what's necessary
|
| 44 |
+
if needs_organism_reload:
|
| 45 |
+
self.load_organisms_and_endonuclease()
|
| 46 |
+
|
| 47 |
+
if needs_annotation_reload:
|
| 48 |
+
self.load_annotation_files()
|
| 49 |
+
|
| 50 |
+
self.logger.debug(f"Updated model data for changes: {changes}")
|
| 51 |
+
|
| 52 |
+
except Exception as e:
|
| 53 |
+
self.logger.error(f"Error updating for file changes: {str(e)}")
|
| 54 |
+
|
| 55 |
def load_organisms_and_endonuclease(self) -> None:
|
| 56 |
+
"""Load organism and endonuclease data from CSPR files"""
|
| 57 |
try:
|
| 58 |
+
# Clear existing data
|
| 59 |
self.data["organism_to_files"] = {}
|
| 60 |
self.data["organism_to_endonuclease"] = {}
|
| 61 |
+
|
| 62 |
cspr_files = glob.glob(os.path.join(self.global_settings.get_db_path(), "*.cspr"))
|
| 63 |
+
|
| 64 |
for file in cspr_files:
|
| 65 |
file_name = os.path.basename(file)
|
| 66 |
file_name_no_ext = file_name[:-5]
|
|
|
|
| 69 |
with open(file, 'r') as f:
|
| 70 |
organism = f.readline().strip().replace("GENOME: ", '')
|
| 71 |
|
| 72 |
+
# Update organism to files mapping
|
| 73 |
+
if organism not in self.data["organism_to_files"]:
|
| 74 |
+
self.data["organism_to_files"][organism] = {}
|
| 75 |
+
self.data["organism_to_files"][organism][endonuclease] = [
|
| 76 |
+
file_name,
|
| 77 |
+
file_name.replace(".cspr", "_repeats.db")
|
| 78 |
+
]
|
| 79 |
|
| 80 |
+
# Update organism to endonuclease mapping
|
| 81 |
+
if organism not in self.data["organism_to_endonuclease"]:
|
| 82 |
+
self.data["organism_to_endonuclease"][organism] = []
|
| 83 |
+
if endonuclease not in self.data["organism_to_endonuclease"][organism]:
|
| 84 |
self.data["organism_to_endonuclease"][organism].append(endonuclease)
|
|
|
|
|
|
|
| 85 |
|
| 86 |
+
self.logger.debug(f"Loaded data for {len(self.data['organism_to_files'])} organisms")
|
| 87 |
+
|
| 88 |
except Exception as e:
|
| 89 |
+
self.logger.error(f"Error loading organisms and endonucleases: {str(e)}")
|
| 90 |
+
raise
|
| 91 |
|
| 92 |
def load_annotation_files(self) -> None:
|
| 93 |
+
"""Load annotation files from the database directory"""
|
| 94 |
try:
|
| 95 |
+
# Get all .gb* files recursively
|
| 96 |
+
annotation_files = glob.glob(
|
| 97 |
+
os.path.join(self.global_settings.get_db_path(), "**", "*.gb*"),
|
| 98 |
+
recursive=True
|
| 99 |
+
)
|
| 100 |
+
|
| 101 |
+
# Process files
|
| 102 |
+
self.data["annotation_files"] = {
|
| 103 |
+
os.path.basename(file) for file in annotation_files
|
| 104 |
+
if not file.endswith('.index') # Exclude index files
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
self.logger.debug(f"Loaded {len(self.data['annotation_files'])} annotation files")
|
| 108 |
+
|
| 109 |
except Exception as e:
|
| 110 |
+
self.logger.error(f"Error loading annotation files: {str(e)}")
|
| 111 |
+
raise
|
| 112 |
|
| 113 |
def get_organism_to_files(self) -> Dict[str, Dict[str, List[str]]]:
|
| 114 |
+
"""Get mapping of organisms to their files"""
|
| 115 |
return self.data['organism_to_files']
|
| 116 |
|
| 117 |
def get_organism_to_endonuclease(self) -> Dict[str, List[str]]:
|
| 118 |
+
"""Get mapping of organisms to their endonucleases"""
|
| 119 |
return self.data.get("organism_to_endonuclease", {})
|
| 120 |
|
| 121 |
def get_annotation_files(self) -> List[str]:
|
| 122 |
+
"""Get list of annotation files"""
|
| 123 |
+
return sorted(self.data.get("annotation_files", set()), key=str.lower)
|
| 124 |
|
| 125 |
def find_targets(self, input_data: dict) -> None:
|
| 126 |
pass
|
|
@@ -1,75 +0,0 @@
|
|
| 1 |
-
import os
|
| 2 |
-
import glob
|
| 3 |
-
from typing import Dict, List, Any
|
| 4 |
-
from utils.ui import show_error
|
| 5 |
-
|
| 6 |
-
class MainWindowModel():
|
| 7 |
-
def __init__(self, global_settings):
|
| 8 |
-
self.global_settings = global_settings
|
| 9 |
-
self.logger = global_settings.get_logger()
|
| 10 |
-
self.data = {}
|
| 11 |
-
self.settings = {}
|
| 12 |
-
self.dbpath = ""
|
| 13 |
-
self.inputstring = ""
|
| 14 |
-
self.anno_name = ""
|
| 15 |
-
self.endo_name = ""
|
| 16 |
-
self.org = ""
|
| 17 |
-
self.genlib_list = []
|
| 18 |
-
self.results_list = []
|
| 19 |
-
|
| 20 |
-
def load_data(self) -> None:
|
| 21 |
-
try:
|
| 22 |
-
self.load_organisms_and_endonuclease()
|
| 23 |
-
self.load_annotation_files()
|
| 24 |
-
except Exception as e:
|
| 25 |
-
self.logger.error(f"Error loading data: {str(e)}")
|
| 26 |
-
show_error(self.global_settings, "Error Loading Data", str(e))
|
| 27 |
-
|
| 28 |
-
def load_organisms_and_endonuclease(self) -> None:
|
| 29 |
-
try:
|
| 30 |
-
self.data["organism_to_files"] = {}
|
| 31 |
-
self.data["organism_to_endonuclease"] = {}
|
| 32 |
-
cspr_files = glob.glob(os.path.join(self.global_settings.get_database_path(), "*.cspr"))
|
| 33 |
-
|
| 34 |
-
for file in cspr_files:
|
| 35 |
-
file_name = os.path.basename(file)
|
| 36 |
-
file_name_no_ext = file_name[:-5]
|
| 37 |
-
endonuclease = file_name_no_ext[file_name_no_ext.rfind("_")+1:]
|
| 38 |
-
|
| 39 |
-
with open(file, 'r') as f:
|
| 40 |
-
organism = f.readline().strip().replace("GENOME: ", '')
|
| 41 |
-
|
| 42 |
-
if organism in self.data["organism_to_files"]:
|
| 43 |
-
self.data["organism_to_files"][organism][endonuclease] = [file_name, file_name.replace(".cspr", "_repeats.db")]
|
| 44 |
-
else:
|
| 45 |
-
self.data["organism_to_files"][organism] = {endonuclease: [file_name, file_name.replace(".cspr", "_repeats.db")]}
|
| 46 |
-
|
| 47 |
-
if organism in self.data["organism_to_endonuclease"]:
|
| 48 |
-
self.data["organism_to_endonuclease"][organism].append(endonuclease)
|
| 49 |
-
else:
|
| 50 |
-
self.data["organism_to_endonuclease"][organism] = [endonuclease]
|
| 51 |
-
|
| 52 |
-
self.logger.debug("Successfully loaded genome and endonuclease data.")
|
| 53 |
-
except Exception as e:
|
| 54 |
-
self.logger.error(f"Error in load_organisms_and_endonuclease: {str(e)}")
|
| 55 |
-
|
| 56 |
-
def load_annotation_files(self) -> None:
|
| 57 |
-
try:
|
| 58 |
-
self.data["annotation_files"] = glob.glob(os.path.join(self.global_settings.CSPR_DB, "**", "*.gb*"), recursive=True)
|
| 59 |
-
self.data["annotation_files"] = [os.path.basename(file) for file in self.data["annotation_files"]]
|
| 60 |
-
self.data["annotation_files"].sort(key=str.lower)
|
| 61 |
-
self.data["annotation_files"].append("None")
|
| 62 |
-
self.logger.debug("Successfully loaded annotation files.")
|
| 63 |
-
except Exception as e:
|
| 64 |
-
self.logger.error(f"Error in load_annotation_files: {str(e)}")
|
| 65 |
-
|
| 66 |
-
def get_organism_to_files(self) -> Dict[str, Dict[str, List[str]]]:
|
| 67 |
-
return self.data.get("organism_to_files", {})
|
| 68 |
-
|
| 69 |
-
def get_organism_to_endonuclease(self) -> Dict[str, List[str]]:
|
| 70 |
-
return self.data.get("organism_to_endonuclease", {})
|
| 71 |
-
|
| 72 |
-
def get_annotation_files(self) -> List[str]:
|
| 73 |
-
return self.data.get("annotation_files", [])
|
| 74 |
-
|
| 75 |
-
# Add other methods that handle data processing, validation, and storage
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,4 +1,6 @@
|
|
| 1 |
import os
|
|
|
|
|
|
|
| 2 |
|
| 3 |
class MultitargetingWindowModel:
|
| 4 |
def __init__(self, global_settings):
|
|
@@ -36,7 +38,83 @@ class MultitargetingWindowModel:
|
|
| 36 |
"""Get repeats data for the seeds table"""
|
| 37 |
if not self.db_file:
|
| 38 |
raise ValueError("Database file not set. Please select an organism and endonuclease first.")
|
| 39 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
|
| 41 |
def get_seed_data(self, seed):
|
| 42 |
"""Get detailed data for a specific seed"""
|
|
@@ -48,15 +126,75 @@ class MultitargetingWindowModel:
|
|
| 48 |
|
| 49 |
def get_seeds_vs_repeats_data(self):
|
| 50 |
"""Get data for seeds vs repeats plot"""
|
| 51 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
|
| 53 |
def get_repeats_vs_seeds_data(self):
|
| 54 |
"""Get data for repeats vs seeds plot"""
|
| 55 |
return self.settings.db_manager.get_repeats_vs_seeds_data(self.db_file)
|
| 56 |
|
| 57 |
def calculate_statistics(self):
|
| 58 |
-
"""Calculate
|
| 59 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
|
| 61 |
def get_sql_settings(self):
|
| 62 |
"""Get current SQL query settings"""
|
|
@@ -130,3 +268,15 @@ class MultitargetingWindowModel:
|
|
| 130 |
except Exception as e:
|
| 131 |
self.logger.error(f"Error getting kstats: {str(e)}")
|
| 132 |
raise
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import os
|
| 2 |
+
import sqlite3
|
| 3 |
+
import statistics
|
| 4 |
|
| 5 |
class MultitargetingWindowModel:
|
| 6 |
def __init__(self, global_settings):
|
|
|
|
| 38 |
"""Get repeats data for the seeds table"""
|
| 39 |
if not self.db_file:
|
| 40 |
raise ValueError("Database file not set. Please select an organism and endonuclease first.")
|
| 41 |
+
|
| 42 |
+
try:
|
| 43 |
+
conn = sqlite3.connect(self.db_file)
|
| 44 |
+
c = conn.cursor()
|
| 45 |
+
|
| 46 |
+
# Use row limit in query
|
| 47 |
+
if self.row_limit == -1: # No limit
|
| 48 |
+
query = "SELECT * FROM repeats ORDER BY count DESC;"
|
| 49 |
+
else:
|
| 50 |
+
query = f"SELECT * FROM repeats ORDER BY count DESC LIMIT 0, {self.row_limit};"
|
| 51 |
+
|
| 52 |
+
results = []
|
| 53 |
+
for repeat in c.execute(query):
|
| 54 |
+
# Extract repeat info
|
| 55 |
+
seed = repeat[0]
|
| 56 |
+
chroms = repeat[1].split(",")
|
| 57 |
+
locs = repeat[2].split(",")
|
| 58 |
+
threes = repeat[3].split(",")
|
| 59 |
+
fives = repeat[4].split(",")
|
| 60 |
+
pams = repeat[5].split(",")
|
| 61 |
+
scores = repeat[6].split(",")
|
| 62 |
+
count = repeat[7]
|
| 63 |
+
|
| 64 |
+
# Calculate average repeats per scaffold
|
| 65 |
+
location_repeat_counts = {}
|
| 66 |
+
for chrom in chroms:
|
| 67 |
+
location_repeat_counts[chrom] = location_repeat_counts.get(chrom, 0) + 1
|
| 68 |
+
avg_per_scaffold = sum(location_repeat_counts.values()) / len(location_repeat_counts)
|
| 69 |
+
avg_per_scaffold = float("%.2f" % avg_per_scaffold)
|
| 70 |
+
|
| 71 |
+
# Find majority sequence
|
| 72 |
+
majority_index = 0
|
| 73 |
+
sequences = ""
|
| 74 |
+
consensus_percent = 0
|
| 75 |
+
|
| 76 |
+
if threes[0] == '':
|
| 77 |
+
majority = max(set(fives), key=fives.count)
|
| 78 |
+
majority_index = fives.index(majority)
|
| 79 |
+
sequences = fives[majority_index] + seed
|
| 80 |
+
consensus_percent = (fives.count(fives[majority_index]) / len(fives)) * 100
|
| 81 |
+
elif fives[0] == '':
|
| 82 |
+
majority = max(set(threes), key=threes.count)
|
| 83 |
+
majority_index = threes.index(majority)
|
| 84 |
+
sequences = seed + threes[majority_index]
|
| 85 |
+
consensus_percent = (threes.count(threes[majority_index]) / len(threes)) * 100
|
| 86 |
+
else:
|
| 87 |
+
# Both 3' and 5' present
|
| 88 |
+
combined_seqs = [f"{fives[i]}{threes[i]}" for i in range(len(threes))]
|
| 89 |
+
majority = max(set(combined_seqs), key=combined_seqs.count)
|
| 90 |
+
majority_index = combined_seqs.index(majority)
|
| 91 |
+
sequences = fives[majority_index] + seed + threes[majority_index]
|
| 92 |
+
consensus_percent = (combined_seqs.count(majority) / len(combined_seqs)) * 100
|
| 93 |
+
|
| 94 |
+
# Determine strand
|
| 95 |
+
strand = "+" if int(locs[majority_index]) >= 0 else "-"
|
| 96 |
+
|
| 97 |
+
# Format consensus percent
|
| 98 |
+
consensus_percent = float("%.1f" % consensus_percent)
|
| 99 |
+
|
| 100 |
+
results.append((
|
| 101 |
+
seed, # Seed sequence
|
| 102 |
+
count, # Total repeats
|
| 103 |
+
avg_per_scaffold, # Average repeats per scaffold
|
| 104 |
+
sequences, # Consensus sequence
|
| 105 |
+
consensus_percent, # Consensus percentage
|
| 106 |
+
scores[majority_index], # Score
|
| 107 |
+
pams[majority_index], # PAM
|
| 108 |
+
strand # Strand
|
| 109 |
+
))
|
| 110 |
+
|
| 111 |
+
c.close()
|
| 112 |
+
conn.close()
|
| 113 |
+
return results
|
| 114 |
+
|
| 115 |
+
except Exception as e:
|
| 116 |
+
self.logger.error(f"Error getting repeats data: {str(e)}")
|
| 117 |
+
raise
|
| 118 |
|
| 119 |
def get_seed_data(self, seed):
|
| 120 |
"""Get detailed data for a specific seed"""
|
|
|
|
| 126 |
|
| 127 |
def get_seeds_vs_repeats_data(self):
|
| 128 |
"""Get data for seeds vs repeats plot"""
|
| 129 |
+
try:
|
| 130 |
+
conn = sqlite3.connect(self.db_file)
|
| 131 |
+
c = conn.cursor()
|
| 132 |
+
|
| 133 |
+
# Query to get count of sequences for each repeat count, ordered by count DESC
|
| 134 |
+
# This matches the original implementation
|
| 135 |
+
query = """
|
| 136 |
+
SELECT count, COUNT(count) as cnt
|
| 137 |
+
FROM repeats
|
| 138 |
+
GROUP BY count
|
| 139 |
+
ORDER BY cnt DESC;
|
| 140 |
+
"""
|
| 141 |
+
|
| 142 |
+
x_vals = [] # Number of repeats
|
| 143 |
+
y_vals = [] # Number of sequences
|
| 144 |
+
|
| 145 |
+
for row in c.execute(query):
|
| 146 |
+
x_vals.append(row[0]) # count
|
| 147 |
+
y_vals.append(row[1]) # cnt
|
| 148 |
+
|
| 149 |
+
# Sort x_vals after collecting all data, just like in original
|
| 150 |
+
x_vals = sorted(x_vals)
|
| 151 |
+
|
| 152 |
+
c.close()
|
| 153 |
+
conn.close()
|
| 154 |
+
|
| 155 |
+
return {
|
| 156 |
+
'x_vals': x_vals,
|
| 157 |
+
'y_vals': y_vals
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
except Exception as e:
|
| 161 |
+
self.logger.error(f"Error getting seeds vs repeats data: {str(e)}")
|
| 162 |
+
return None
|
| 163 |
|
| 164 |
def get_repeats_vs_seeds_data(self):
|
| 165 |
"""Get data for repeats vs seeds plot"""
|
| 166 |
return self.settings.db_manager.get_repeats_vs_seeds_data(self.db_file)
|
| 167 |
|
| 168 |
def calculate_statistics(self):
|
| 169 |
+
"""Calculate statistics for the repeats data"""
|
| 170 |
+
try:
|
| 171 |
+
conn = sqlite3.connect(self.db_file)
|
| 172 |
+
c = conn.cursor()
|
| 173 |
+
|
| 174 |
+
# Get all repeat counts
|
| 175 |
+
counts = []
|
| 176 |
+
for obj in c.execute("SELECT count FROM repeats;"):
|
| 177 |
+
counts.append(obj[0])
|
| 178 |
+
|
| 179 |
+
if not counts:
|
| 180 |
+
return None
|
| 181 |
+
|
| 182 |
+
# Calculate statistics
|
| 183 |
+
stats = {
|
| 184 |
+
'average': statistics.mean(counts),
|
| 185 |
+
'mode': statistics.mode(counts),
|
| 186 |
+
'median': statistics.median(counts),
|
| 187 |
+
'repeat_count': len(counts)
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
c.close()
|
| 191 |
+
conn.close()
|
| 192 |
+
|
| 193 |
+
return stats
|
| 194 |
+
|
| 195 |
+
except Exception as e:
|
| 196 |
+
self.logger.error(f"Error calculating statistics: {str(e)}")
|
| 197 |
+
raise
|
| 198 |
|
| 199 |
def get_sql_settings(self):
|
| 200 |
"""Get current SQL query settings"""
|
|
|
|
| 268 |
except Exception as e:
|
| 269 |
self.logger.error(f"Error getting kstats: {str(e)}")
|
| 270 |
raise
|
| 271 |
+
|
| 272 |
+
def set_row_limit(self, limit):
|
| 273 |
+
"""Set the maximum number of rows to return"""
|
| 274 |
+
try:
|
| 275 |
+
self.row_limit = int(limit)
|
| 276 |
+
except ValueError:
|
| 277 |
+
self.logger.error(f"Invalid row limit value: {limit}")
|
| 278 |
+
self.row_limit = 1000 # Reset to default if invalid
|
| 279 |
+
|
| 280 |
+
def get_row_limit(self):
|
| 281 |
+
"""Get current row limit setting"""
|
| 282 |
+
return self.row_limit
|
|
@@ -71,14 +71,14 @@ class NewEndonucleaseModel(QObject):
|
|
| 71 |
pam = ','.join([x.strip() for x in pam.split(',')])
|
| 72 |
|
| 73 |
argument_list = [
|
| 74 |
-
form_data['endonuclease_organism'],
|
| 75 |
form_data['endonuclease_abbreviation'],
|
| 76 |
-
form_data['endonuclease_CRISPR_type'],
|
| 77 |
pam,
|
| 78 |
form_data['endonuclease_five_prime_length'],
|
| 79 |
form_data['endonuclease_seed_length'],
|
| 80 |
form_data['endonuclease_three_prime_length'],
|
| 81 |
form_data['endonuclease_direction'],
|
|
|
|
|
|
|
| 82 |
form_data['endonuclease_on_target_scoring'],
|
| 83 |
form_data['endonuclease_off_target_scoring']
|
| 84 |
]
|
|
|
|
| 71 |
pam = ','.join([x.strip() for x in pam.split(',')])
|
| 72 |
|
| 73 |
argument_list = [
|
|
|
|
| 74 |
form_data['endonuclease_abbreviation'],
|
|
|
|
| 75 |
pam,
|
| 76 |
form_data['endonuclease_five_prime_length'],
|
| 77 |
form_data['endonuclease_seed_length'],
|
| 78 |
form_data['endonuclease_three_prime_length'],
|
| 79 |
form_data['endonuclease_direction'],
|
| 80 |
+
form_data['endonuclease_organism'],
|
| 81 |
+
form_data['endonuclease_CRISPR_type'],
|
| 82 |
form_data['endonuclease_on_target_scoring'],
|
| 83 |
form_data['endonuclease_off_target_scoring']
|
| 84 |
]
|
|
@@ -88,7 +88,7 @@ class NewGenomeWindowModel(QObject):
|
|
| 88 |
f'{db_path}',
|
| 89 |
f'{self.settings.get_casper_info_path()}',
|
| 90 |
f'{file_path}',
|
| 91 |
-
f'
|
| 92 |
'notes',
|
| 93 |
f'DATA:{endonuclease_data["endonuclease_on_target_scoring"]}'
|
| 94 |
]
|
|
@@ -136,7 +136,6 @@ class NewGenomeWindowModel(QObject):
|
|
| 136 |
self.current_job_progress = 0
|
| 137 |
return self.update_total_progress()
|
| 138 |
|
| 139 |
-
|
| 140 |
def validate_fasta_file(self, file_path):
|
| 141 |
return file_path.lower().endswith(('.fa', '.fna', '.fasta'))
|
| 142 |
|
|
|
|
| 88 |
f'{db_path}',
|
| 89 |
f'{self.settings.get_casper_info_path()}',
|
| 90 |
f'{file_path}',
|
| 91 |
+
f'{organism_name} {strain}',
|
| 92 |
'notes',
|
| 93 |
f'DATA:{endonuclease_data["endonuclease_on_target_scoring"]}'
|
| 94 |
]
|
|
|
|
| 136 |
self.current_job_progress = 0
|
| 137 |
return self.update_total_progress()
|
| 138 |
|
|
|
|
| 139 |
def validate_fasta_file(self, file_path):
|
| 140 |
return file_path.lower().endswith(('.fa', '.fna', '.fasta'))
|
| 141 |
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
DETAILED OUTPUT
|
| 2 |
+
CACTTATGACCGGGCAACTT:0.000000
|
| 3 |
+
ACACTTATGACCGGGCAACT:0.080598
|
| 4 |
+
0.080598,1,-100695,ACAATTACGCCCGGGCAACC
|
| 5 |
+
TCAAAATAGCCCAAGTTGCC:0.000000
|
| 6 |
+
ATTTTGCTACACTTATGACC:0.000000
|
| 7 |
+
AATTTTGCTACACTTATGAC:0.000000
|
| 8 |
+
GGGAATACTCCCTTTTATTG:0.000000
|
| 9 |
+
GCAAAATTATCCTCAATAAA:0.018309
|
| 10 |
+
0.018309,1,1937923,GCAAAGTTTTCCTCAATATT
|
| 11 |
+
CAAAATTATCCTCAATAAAA:0.107086
|
| 12 |
+
0.059761,1,2361124,CACATTTACCCTCAATGAAA
|
| 13 |
+
0.154411,1,4041223,CAAATCTATACTGAATAAAA
|
| 14 |
+
CAGCTACAACCCGTGGCGGA:0.000000
|
| 15 |
+
CCAGCTACAACCCGTGGCGG:0.106259
|
| 16 |
+
0.106259,1,4257161,ACAACTGCAAGCCGTGGCGG
|
| 17 |
+
CCGCCAGCTACAACCCGTGG:0.000000
|
| 18 |
+
AGGGAGTATTCCCTCCGCCA:0.000000
|
| 19 |
+
GACCCGCCAGCTACAACCCG:0.000000
|
| 20 |
+
GGGAGTATTCCCTCCGCCAC:0.000000
|
| 21 |
+
CCTCCGCCACGGGTTGTAGC:0.129554
|
| 22 |
+
0.129554,1,-165804,GTTACGCCACGGGTTGTAGA
|
| 23 |
+
CCGCCACGGGTTGTAGCTGG:0.000000
|
| 24 |
+
CGCCACGGGTTGTAGCTGGC:0.000000
|
| 25 |
+
GGACTACCAACGTTCACCAC:0.221431
|
| 26 |
+
0.234722,1,-469831,TCGCTACCATCGTTCACCAC
|
| 27 |
+
0.208141,1,2847166,GGAGAACCGACGGTCACCAC
|
| 28 |
+
AGATAGTGTTCGTAATCCAG:0.000000
|
| 29 |
+
CGTAATCCAGTGGTGAACGT:0.000000
|
|
File without changes
|
|
@@ -0,0 +1,457 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from math import e
|
| 2 |
+
import os
|
| 3 |
+
import platform
|
| 4 |
+
from PyQt6.QtCore import QProcess, pyqtSignal, QObject
|
| 5 |
+
import logging
|
| 6 |
+
import traceback
|
| 7 |
+
|
| 8 |
+
class OffTargetModel(QObject):
|
| 9 |
+
# Update signal to emit tuple of (scores, details)
|
| 10 |
+
results_ready = pyqtSignal(tuple) # Emits (scores_dict, details_dict)
|
| 11 |
+
progress_updated = pyqtSignal(int, str) # Emits (progress_value, status_message)
|
| 12 |
+
|
| 13 |
+
def __init__(self, global_settings):
|
| 14 |
+
super().__init__() # Make sure OffTargetModel inherits from QObject
|
| 15 |
+
self.global_settings = global_settings
|
| 16 |
+
self.logger = global_settings.get_logger()
|
| 17 |
+
self.process = None
|
| 18 |
+
self.organisms_to_files = {}
|
| 19 |
+
self.organisms_to_endos = {}
|
| 20 |
+
self._current_parameters = None
|
| 21 |
+
self._load_organisms_and_endos()
|
| 22 |
+
|
| 23 |
+
def _load_organisms_and_endos(self):
|
| 24 |
+
"""Load available organisms and endonucleases from CSPR files"""
|
| 25 |
+
try:
|
| 26 |
+
cspr_files = [f for f in os.listdir(self.global_settings.get_db_path())
|
| 27 |
+
if f.endswith('.cspr')]
|
| 28 |
+
|
| 29 |
+
for file in cspr_files:
|
| 30 |
+
newname = file[:-4]
|
| 31 |
+
endo = newname[newname.rfind("_") + 1:-1]
|
| 32 |
+
|
| 33 |
+
with open(os.path.join(self.global_settings.get_db_path(), file), 'r') as f:
|
| 34 |
+
species = f.readline().strip().replace("GENOME: ", "")
|
| 35 |
+
|
| 36 |
+
# Store file mappings
|
| 37 |
+
if species not in self.organisms_to_files:
|
| 38 |
+
self.organisms_to_files[species] = {}
|
| 39 |
+
self.organisms_to_files[species][endo] = [
|
| 40 |
+
file,
|
| 41 |
+
file.replace(".cspr", "_repeats.db")
|
| 42 |
+
]
|
| 43 |
+
|
| 44 |
+
# Store endo mappings
|
| 45 |
+
if species not in self.organisms_to_endos:
|
| 46 |
+
self.organisms_to_endos[species] = []
|
| 47 |
+
if endo not in self.organisms_to_endos[species]:
|
| 48 |
+
self.organisms_to_endos[species].append(endo)
|
| 49 |
+
|
| 50 |
+
except Exception as e:
|
| 51 |
+
self.logger.error(f"Error loading organisms and endonucleases: {str(e)}")
|
| 52 |
+
raise
|
| 53 |
+
|
| 54 |
+
def get_organisms(self):
|
| 55 |
+
"""Get list of available organisms"""
|
| 56 |
+
return sorted(self.organisms_to_files.keys())
|
| 57 |
+
|
| 58 |
+
def get_endonucleases(self, organism):
|
| 59 |
+
"""Get available endonucleases for given organism"""
|
| 60 |
+
return sorted(self.organisms_to_endos.get(organism, []))
|
| 61 |
+
|
| 62 |
+
def get_file_paths(self, organism, endonuclease):
|
| 63 |
+
"""Get CSPR and DB file paths for given organism and endonuclease"""
|
| 64 |
+
try:
|
| 65 |
+
files = self.organisms_to_files[organism][endonuclease]
|
| 66 |
+
return {
|
| 67 |
+
'cspr_file': os.path.join(self.global_settings.get_db_path(), files[0]),
|
| 68 |
+
'db_file': os.path.join(self.global_settings.get_db_path(), files[1])
|
| 69 |
+
}
|
| 70 |
+
except KeyError:
|
| 71 |
+
self.logger.error(f"No files found for {organism} and {endonuclease}")
|
| 72 |
+
return None
|
| 73 |
+
|
| 74 |
+
def get_program_command(self):
|
| 75 |
+
if platform.system() == 'Windows':
|
| 76 |
+
return 'OT_Win.exe'
|
| 77 |
+
else:
|
| 78 |
+
return 'OT_Lin' if platform.system() == 'Linux' else 'OT_Mac'
|
| 79 |
+
|
| 80 |
+
def start_analysis(self, parameters):
|
| 81 |
+
"""Start off-target analysis with given parameters"""
|
| 82 |
+
try:
|
| 83 |
+
self._current_parameters = parameters
|
| 84 |
+
# Create QProcess if not exists
|
| 85 |
+
if not self.process:
|
| 86 |
+
self.process = QProcess()
|
| 87 |
+
# Connect process signals for logging
|
| 88 |
+
self.process.readyReadStandardOutput.connect(self._handle_process_output)
|
| 89 |
+
self.process.readyReadStandardError.connect(self._handle_process_error)
|
| 90 |
+
self.process.finished.connect(self._handle_process_finished)
|
| 91 |
+
|
| 92 |
+
# Get file paths
|
| 93 |
+
files = self.get_file_paths(parameters['organism'], parameters['endonuclease'])
|
| 94 |
+
if not files:
|
| 95 |
+
raise ValueError("Could not get required file paths")
|
| 96 |
+
|
| 97 |
+
# Build command
|
| 98 |
+
program_path, cmd = self._build_command(parameters, files)
|
| 99 |
+
|
| 100 |
+
# Log analysis start and parameters
|
| 101 |
+
self.logger.debug("=== Starting off-target analysis ===")
|
| 102 |
+
self.logger.debug(f"Organism: {parameters['organism']}")
|
| 103 |
+
self.logger.debug(f"Endonuclease: {parameters['endonuclease']}")
|
| 104 |
+
self.logger.debug(f"Number of targets: {len(parameters.get('targets', []))}")
|
| 105 |
+
self.logger.debug(f"Max mismatches: {parameters['max_mismatches']}")
|
| 106 |
+
self.logger.debug(f"Tolerance: {parameters['tolerance']}")
|
| 107 |
+
self.logger.debug(f"Average output: {parameters['average_output']}")
|
| 108 |
+
|
| 109 |
+
# Set working directory
|
| 110 |
+
off_target_dir = self.global_settings.get_off_target_dir_path()
|
| 111 |
+
self.process.setWorkingDirectory(off_target_dir)
|
| 112 |
+
|
| 113 |
+
# Set executable permissions on Unix systems
|
| 114 |
+
if platform.system() != 'Windows':
|
| 115 |
+
import stat
|
| 116 |
+
st = os.stat(program_path)
|
| 117 |
+
os.chmod(program_path, st.st_mode | stat.S_IEXEC)
|
| 118 |
+
|
| 119 |
+
# Verify temp file exists and has content
|
| 120 |
+
temp_path = os.path.join(off_target_dir, 'temp.txt')
|
| 121 |
+
if os.path.exists(temp_path):
|
| 122 |
+
with open(temp_path, 'r') as f:
|
| 123 |
+
content = f.read()
|
| 124 |
+
self.logger.debug(f"Temp file contents:\n{content}")
|
| 125 |
+
else:
|
| 126 |
+
self.logger.error("Temp file does not exist before starting process")
|
| 127 |
+
|
| 128 |
+
# Start process
|
| 129 |
+
self.logger.debug("Starting QProcess with command:")
|
| 130 |
+
self.logger.debug(cmd)
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
example_cmd = [
|
| 134 |
+
"/Users/admin/Documents/proj/CASPERtest/CASPERapp/src/models/OffTarget/temp.txt",
|
| 135 |
+
"spCas9",
|
| 136 |
+
"/Users/admin/Documents/CASPERdb2/eck_12_spCas9.cspr",
|
| 137 |
+
"/Users/admin/Documents/CASPERdb2/eck_12_spCas9_repeats.db",
|
| 138 |
+
"/Users/admin/Documents/CASPERdb2/testtttt",
|
| 139 |
+
"/Users/admin/Documents/proj/CASPERtest/CASPERapp/config/CASPERinfo",
|
| 140 |
+
"4",
|
| 141 |
+
"0.05",
|
| 142 |
+
"FALSE",
|
| 143 |
+
"TRUE",
|
| 144 |
+
"MATRIX:HSU MATRIX-spCas9-2013"
|
| 145 |
+
]
|
| 146 |
+
|
| 147 |
+
print(f"cmd: {cmd}")
|
| 148 |
+
|
| 149 |
+
print(f"example_cmd: {example_cmd}")
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
self.process.start(str(program_path), cmd)
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
return True
|
| 157 |
+
|
| 158 |
+
except Exception as e:
|
| 159 |
+
self.logger.error(f"Error starting analysis: {str(e)}")
|
| 160 |
+
self.logger.error(f"Stack trace: {traceback.format_exc()}")
|
| 161 |
+
raise
|
| 162 |
+
|
| 163 |
+
def _handle_process_output(self):
|
| 164 |
+
"""Handle standard output from process"""
|
| 165 |
+
try:
|
| 166 |
+
output = self.process.readAllStandardOutput().data().decode()
|
| 167 |
+
self.logger.debug(f"Process output: {output}")
|
| 168 |
+
|
| 169 |
+
# Update progress based on output content
|
| 170 |
+
if "Reading in Data" in output:
|
| 171 |
+
self.progress_updated.emit(10, "Reading data...")
|
| 172 |
+
elif "Loading data for algorithm" in output:
|
| 173 |
+
self.progress_updated.emit(25, "Loading algorithm data...")
|
| 174 |
+
elif "Running OffTarget Analysis" in output:
|
| 175 |
+
self.progress_updated.emit(50, "Running analysis...")
|
| 176 |
+
elif "Writing Output" in output:
|
| 177 |
+
self.progress_updated.emit(75, "Writing results...")
|
| 178 |
+
elif "Analysis Complete" in output:
|
| 179 |
+
self.progress_updated.emit(90, "Finalizing...")
|
| 180 |
+
|
| 181 |
+
except Exception as e:
|
| 182 |
+
self.logger.error(f"Error handling process output: {str(e)}")
|
| 183 |
+
|
| 184 |
+
def _handle_process_error(self):
|
| 185 |
+
"""Handle standard error from process"""
|
| 186 |
+
try:
|
| 187 |
+
error = self.process.readAllStandardError().data().decode()
|
| 188 |
+
self.logger.error(f"Process error output: {error}")
|
| 189 |
+
except Exception as e:
|
| 190 |
+
self.logger.error(f"Error handling process error output: {str(e)}")
|
| 191 |
+
|
| 192 |
+
def _handle_process_finished(self, exit_code, exit_status):
|
| 193 |
+
"""Handle process completion and parse results"""
|
| 194 |
+
try:
|
| 195 |
+
self.logger.debug(f"Process finished with exit code: {exit_code}")
|
| 196 |
+
|
| 197 |
+
if exit_code == 0: # Success
|
| 198 |
+
# Get output file path
|
| 199 |
+
output_path = self._get_output_path(self._current_parameters)
|
| 200 |
+
|
| 201 |
+
if os.path.exists(output_path):
|
| 202 |
+
# Parse results
|
| 203 |
+
results, detailed_results = self._parse_off_target_results(output_path)
|
| 204 |
+
if results is not None:
|
| 205 |
+
# Update progress to 100%
|
| 206 |
+
self.progress_updated.emit(100, "Analysis complete")
|
| 207 |
+
# Emit results signal with both scores and details
|
| 208 |
+
self.results_ready.emit((results, detailed_results))
|
| 209 |
+
self.logger.debug(f"Parsed and emitted {len(results)} off-target results with {len(detailed_results)} detailed results")
|
| 210 |
+
else:
|
| 211 |
+
self.progress_updated.emit(0, "Analysis failed - no results")
|
| 212 |
+
self.results_ready.emit(({}, {})) # Emit empty results
|
| 213 |
+
else:
|
| 214 |
+
self.logger.error(f"Output file not found: {output_path}")
|
| 215 |
+
self.progress_updated.emit(0, "Analysis failed - no output file")
|
| 216 |
+
self.results_ready.emit(({}, {})) # Emit empty results
|
| 217 |
+
else:
|
| 218 |
+
self.logger.error(f"Process failed with exit code: {exit_code}")
|
| 219 |
+
self.progress_updated.emit(0, f"Analysis failed with code {exit_code}")
|
| 220 |
+
self.results_ready.emit(({}, {})) # Emit empty results
|
| 221 |
+
|
| 222 |
+
# Always cleanup temp file after process finishes
|
| 223 |
+
self._cleanup_temp_file()
|
| 224 |
+
|
| 225 |
+
except Exception as e:
|
| 226 |
+
self.logger.error(f"Error in process finished handler: {str(e)}")
|
| 227 |
+
self.progress_updated.emit(0, f"Error: {str(e)}")
|
| 228 |
+
self.results_ready.emit(({}, {})) # Emit empty results
|
| 229 |
+
# Ensure cleanup happens even if there's an error
|
| 230 |
+
self._cleanup_temp_file()
|
| 231 |
+
|
| 232 |
+
def _cleanup_temp_file(self):
|
| 233 |
+
"""Clean up temporary input file"""
|
| 234 |
+
try:
|
| 235 |
+
# Get path to temp file
|
| 236 |
+
db_path = self.global_settings.get_db_path()
|
| 237 |
+
temp_path = os.path.join(db_path, 'temp.txt')
|
| 238 |
+
|
| 239 |
+
# Remove temp file if it exists
|
| 240 |
+
if os.path.exists(temp_path):
|
| 241 |
+
os.remove(temp_path)
|
| 242 |
+
self.logger.debug(f"Removed temp file: {temp_path}")
|
| 243 |
+
|
| 244 |
+
except Exception as e:
|
| 245 |
+
self.logger.error(f"Error cleaning up temp file: {str(e)}")
|
| 246 |
+
|
| 247 |
+
def _parse_off_target_results(self, file_path):
|
| 248 |
+
"""Parse off-target analysis results file"""
|
| 249 |
+
try:
|
| 250 |
+
results = {}
|
| 251 |
+
detailed_results = {}
|
| 252 |
+
current_sequence = None
|
| 253 |
+
details = []
|
| 254 |
+
|
| 255 |
+
with open(file_path, 'r') as f:
|
| 256 |
+
lines = f.readlines()
|
| 257 |
+
|
| 258 |
+
output_type = lines[0].strip()
|
| 259 |
+
|
| 260 |
+
# Process based on output type
|
| 261 |
+
if output_type == "DETAILED OUTPUT":
|
| 262 |
+
for line in lines[1:]: # Skip header
|
| 263 |
+
line = line.strip()
|
| 264 |
+
if not line:
|
| 265 |
+
continue
|
| 266 |
+
|
| 267 |
+
if ':' in line: # This is a sequence line
|
| 268 |
+
# Save previous sequence details if they exist
|
| 269 |
+
if current_sequence and details:
|
| 270 |
+
detailed_results[current_sequence] = details
|
| 271 |
+
|
| 272 |
+
# Start new sequence
|
| 273 |
+
parts = line.split(':')
|
| 274 |
+
current_sequence = parts[0].strip()
|
| 275 |
+
try:
|
| 276 |
+
score = float(parts[1].strip())
|
| 277 |
+
results[current_sequence] = score
|
| 278 |
+
except ValueError:
|
| 279 |
+
self.logger.warning(f"Invalid score format in line: {line}")
|
| 280 |
+
details = []
|
| 281 |
+
else: # This is a detail line
|
| 282 |
+
details.append(line)
|
| 283 |
+
|
| 284 |
+
# Don't forget the last sequence
|
| 285 |
+
if current_sequence and details:
|
| 286 |
+
detailed_results[current_sequence] = details
|
| 287 |
+
|
| 288 |
+
elif output_type == "AVG OUTPUT":
|
| 289 |
+
for line in lines[1:]: # Skip header
|
| 290 |
+
line = line.strip()
|
| 291 |
+
if not line:
|
| 292 |
+
continue
|
| 293 |
+
|
| 294 |
+
parts = line.split(':')
|
| 295 |
+
if len(parts) == 2:
|
| 296 |
+
sequence = parts[0].strip()
|
| 297 |
+
try:
|
| 298 |
+
score = float(parts[1].strip())
|
| 299 |
+
results[sequence] = score
|
| 300 |
+
except ValueError:
|
| 301 |
+
self.logger.warning(f"Invalid score format in line: {line}")
|
| 302 |
+
|
| 303 |
+
# Return both results and detailed_results
|
| 304 |
+
return results, detailed_results
|
| 305 |
+
|
| 306 |
+
except Exception as e:
|
| 307 |
+
self.logger.error(f"Error parsing off-target results: {str(e)}")
|
| 308 |
+
return None, None
|
| 309 |
+
|
| 310 |
+
def _build_command(self, parameters, files):
|
| 311 |
+
"""Build command string for off-target analysis"""
|
| 312 |
+
try:
|
| 313 |
+
off_target_dir = self.global_settings.get_off_target_dir_path()
|
| 314 |
+
|
| 315 |
+
# Get executable path based on platform
|
| 316 |
+
if platform.system() == 'Windows':
|
| 317 |
+
exe_name = 'OT_Win.exe'
|
| 318 |
+
elif platform.system() == 'Linux':
|
| 319 |
+
exe_name = 'OT_Lin'
|
| 320 |
+
else:
|
| 321 |
+
exe_name = 'OT_Mac'
|
| 322 |
+
|
| 323 |
+
db_path = self.global_settings.get_db_path()
|
| 324 |
+
|
| 325 |
+
# Build paths with quotes
|
| 326 |
+
program_path = f'{os.path.join(off_target_dir, exe_name)}'
|
| 327 |
+
temp_path = f'{os.path.join(db_path, "temp.txt")}'
|
| 328 |
+
cspr_path = f'{files["cspr_file"]}'
|
| 329 |
+
db_path = f'{files["db_file"]}'
|
| 330 |
+
output_path = f'{self._get_output_path(parameters)}'
|
| 331 |
+
casper_info_path = f'{self.global_settings.get_casper_info_path()}'
|
| 332 |
+
endo = f'{parameters["endonuclease"]}'
|
| 333 |
+
|
| 334 |
+
# Build command exactly as in old version
|
| 335 |
+
cmd_parts = [
|
| 336 |
+
temp_path,
|
| 337 |
+
endo,
|
| 338 |
+
cspr_path,
|
| 339 |
+
db_path,
|
| 340 |
+
output_path,
|
| 341 |
+
casper_info_path,
|
| 342 |
+
str(parameters['max_mismatches']),
|
| 343 |
+
str(parameters['tolerance']),
|
| 344 |
+
'FALSE' if parameters['average_output'] else 'TRUE',
|
| 345 |
+
'TRUE' if parameters['average_output'] else 'FALSE',
|
| 346 |
+
f'{self._get_hsu_value(parameters)}'
|
| 347 |
+
]
|
| 348 |
+
|
| 349 |
+
return program_path, cmd_parts
|
| 350 |
+
|
| 351 |
+
except Exception as e:
|
| 352 |
+
self.logger.error(f"Error building command: {str(e)}")
|
| 353 |
+
raise
|
| 354 |
+
|
| 355 |
+
def _get_output_path(self, parameters):
|
| 356 |
+
"""Get output file path"""
|
| 357 |
+
print(f"parameters.get('output_filename'): {parameters.get('output_filename')}")
|
| 358 |
+
if parameters.get('output_filename'): # If filename exists
|
| 359 |
+
return os.path.join(
|
| 360 |
+
self.global_settings.get_db_path(),
|
| 361 |
+
parameters['output_filename']
|
| 362 |
+
)
|
| 363 |
+
return os.path.join(
|
| 364 |
+
self.global_settings.get_off_target_dir_path(),
|
| 365 |
+
'local_output.txt'
|
| 366 |
+
)
|
| 367 |
+
|
| 368 |
+
def _get_hsu_value(self, parameters):
|
| 369 |
+
"""Get HSU matrix name for endonuclease from CASPERinfo file"""
|
| 370 |
+
try:
|
| 371 |
+
casper_info_path = self.global_settings.get_casper_info_path()
|
| 372 |
+
endo = parameters['endonuclease']
|
| 373 |
+
|
| 374 |
+
with open(casper_info_path, 'r') as f:
|
| 375 |
+
lines = f.readlines()
|
| 376 |
+
|
| 377 |
+
# Find ENDONUCLEASES section
|
| 378 |
+
for i, line in enumerate(lines):
|
| 379 |
+
if line.strip() == "ENDONUCLEASES":
|
| 380 |
+
# Search through endonuclease entries
|
| 381 |
+
for entry_line in lines[i+1:]:
|
| 382 |
+
if entry_line.strip() == "-----------------------------------------------------------":
|
| 383 |
+
break
|
| 384 |
+
|
| 385 |
+
# Parse endonuclease entry
|
| 386 |
+
parts = entry_line.strip().split(';')
|
| 387 |
+
if len(parts) >= 10: # Make sure we have enough parts
|
| 388 |
+
endo_name = parts[1] # The endonuclease name is the second part
|
| 389 |
+
if endo_name == endo:
|
| 390 |
+
# Get full HSU matrix name (last part)
|
| 391 |
+
hsu_matrix = parts[-1]
|
| 392 |
+
# Format as "MATRIX:HSU MATRIX-{endo}-{year}"
|
| 393 |
+
matrix_name = f"MATRIX:{hsu_matrix}"
|
| 394 |
+
self.logger.debug(f"Found HSU matrix {matrix_name} for {endo}")
|
| 395 |
+
return matrix_name
|
| 396 |
+
|
| 397 |
+
# If we didn't find a match, use default
|
| 398 |
+
self.logger.warning(f"Could not find HSU matrix for {endo}, using default")
|
| 399 |
+
return "MATRIX:HSU MATRIX-spCas9-2013" # Default matrix
|
| 400 |
+
|
| 401 |
+
except Exception as e:
|
| 402 |
+
self.logger.warning(f"Error getting HSU matrix: {str(e)}, using default")
|
| 403 |
+
return "MATRIX:HSU MATRIX-spCas9-2013" # Default matrix
|
| 404 |
+
|
| 405 |
+
def stop_analysis(self):
|
| 406 |
+
"""Stop running analysis"""
|
| 407 |
+
try:
|
| 408 |
+
if self.process:
|
| 409 |
+
self.process.kill()
|
| 410 |
+
self.process = None
|
| 411 |
+
|
| 412 |
+
# Clean up temp file when analysis is stopped
|
| 413 |
+
self._cleanup_temp_file()
|
| 414 |
+
|
| 415 |
+
except Exception as e:
|
| 416 |
+
self.logger.error(f"Error stopping analysis: {str(e)}")
|
| 417 |
+
|
| 418 |
+
def cleanup(self):
|
| 419 |
+
"""Clean up any temporary files"""
|
| 420 |
+
try:
|
| 421 |
+
# Only clean up temp file
|
| 422 |
+
temp_path = os.path.join(
|
| 423 |
+
self.global_settings.get_off_target_dir_path(),
|
| 424 |
+
'temp.txt'
|
| 425 |
+
)
|
| 426 |
+
if os.path.exists(temp_path):
|
| 427 |
+
os.remove(temp_path)
|
| 428 |
+
self.logger.debug(f"Removed temp file: {temp_path}")
|
| 429 |
+
|
| 430 |
+
# Don't remove local_output.txt here - let it be handled by the process
|
| 431 |
+
|
| 432 |
+
except Exception as e:
|
| 433 |
+
self.logger.error(f"Error in cleanup: {str(e)}")
|
| 434 |
+
|
| 435 |
+
def _write_targets_to_temp(self, targets):
|
| 436 |
+
"""Write target sequences to temporary file for analysis"""
|
| 437 |
+
try:
|
| 438 |
+
db_path = self.global_settings.get_db_path()
|
| 439 |
+
temp_path = os.path.join(db_path, 'temp.txt')
|
| 440 |
+
|
| 441 |
+
self.logger.debug(f"Writing targets to temp file: {temp_path}")
|
| 442 |
+
|
| 443 |
+
with open(temp_path, 'w') as f:
|
| 444 |
+
print(targets)
|
| 445 |
+
for target in targets:
|
| 446 |
+
# Format: position;sequence;pam;score;strand
|
| 447 |
+
entry = f"{target['location'].split('-')[0]};{target['sequence']};{target['pam']};{target['score']};{target['strand']}\n"
|
| 448 |
+
f.write(entry)
|
| 449 |
+
|
| 450 |
+
self.logger.debug(f"Successfully wrote {len(targets)} targets to temp file")
|
| 451 |
+
|
| 452 |
+
# Store temp path for cleanup
|
| 453 |
+
self._temp_path = temp_path
|
| 454 |
+
|
| 455 |
+
except Exception as e:
|
| 456 |
+
self.logger.error(f"Error writing targets to temp file: {str(e)}")
|
| 457 |
+
raise
|
|
@@ -65,6 +65,9 @@ class PopulationAnalysisWindowModel:
|
|
| 65 |
# Get organism mappings from database manager
|
| 66 |
organisms_to_files, organisms_to_endos = self.settings.db_manager.get_organisms_and_endos()
|
| 67 |
|
|
|
|
|
|
|
|
|
|
| 68 |
# Process each organism that has this endonuclease
|
| 69 |
for organism, endos in organisms_to_endos.items():
|
| 70 |
if endo in endos:
|
|
@@ -75,12 +78,18 @@ class PopulationAnalysisWindowModel:
|
|
| 75 |
self.logger.warning(f"Database file not found: {db_file}")
|
| 76 |
continue
|
| 77 |
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
|
| 85 |
self.logger.info(f"Found {len(org_files)} organism files")
|
| 86 |
except Exception as e:
|
|
@@ -89,43 +98,79 @@ class PopulationAnalysisWindowModel:
|
|
| 89 |
return org_files
|
| 90 |
|
| 91 |
def get_shared_seeds(self, db_files, limit=False):
|
|
|
|
| 92 |
try:
|
| 93 |
-
|
| 94 |
|
| 95 |
-
|
|
|
|
|
|
|
| 96 |
new_c = new_conn.cursor()
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
new_c.execute("PRAGMA
|
| 100 |
-
new_c.execute("
|
| 101 |
-
new_c.execute("
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
except Exception as e:
|
| 128 |
-
|
|
|
|
|
|
|
| 129 |
return [] if limit else 0
|
| 130 |
|
| 131 |
def get_seed_data(self, seed, db_files):
|
|
@@ -148,22 +193,52 @@ class PopulationAnalysisWindowModel:
|
|
| 148 |
return data
|
| 149 |
|
| 150 |
def get_heatmap_data(self, db_files):
|
|
|
|
| 151 |
try:
|
| 152 |
size = len(db_files)
|
| 153 |
arr = [[0 for _ in range(size)] for _ in range(size)]
|
| 154 |
|
|
|
|
| 155 |
for i, j in itertools.combinations(range(size), 2):
|
| 156 |
-
|
| 157 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
|
|
|
|
| 159 |
for i in range(size):
|
| 160 |
with sqlite3.connect(db_files[i]) as conn:
|
| 161 |
c = conn.cursor()
|
| 162 |
arr[i][i] = c.execute("SELECT COUNT(*) FROM repeats").fetchone()[0]
|
| 163 |
|
| 164 |
return arr
|
|
|
|
| 165 |
except Exception as e:
|
| 166 |
-
|
|
|
|
|
|
|
| 167 |
return []
|
| 168 |
|
| 169 |
def get_seed_locations(self, seeds, db_files):
|
|
|
|
| 65 |
# Get organism mappings from database manager
|
| 66 |
organisms_to_files, organisms_to_endos = self.settings.db_manager.get_organisms_and_endos()
|
| 67 |
|
| 68 |
+
# Create a list to sort alphabetically
|
| 69 |
+
sorted_organisms = []
|
| 70 |
+
|
| 71 |
# Process each organism that has this endonuclease
|
| 72 |
for organism, endos in organisms_to_endos.items():
|
| 73 |
if endo in endos:
|
|
|
|
| 78 |
self.logger.warning(f"Database file not found: {db_file}")
|
| 79 |
continue
|
| 80 |
|
| 81 |
+
sorted_organisms.append((organism, cspr_file, db_file))
|
| 82 |
+
|
| 83 |
+
# Sort organisms alphabetically by organism name
|
| 84 |
+
sorted_organisms.sort(key=lambda x: x[0].lower())
|
| 85 |
+
|
| 86 |
+
# Store the sorted results
|
| 87 |
+
for index, (organism, cspr_file, db_file) in enumerate(sorted_organisms):
|
| 88 |
+
org_files.append((organism, cspr_file, db_file))
|
| 89 |
+
|
| 90 |
+
# Store the mapping for later use
|
| 91 |
+
self.index_to_cspr[index] = cspr_file
|
| 92 |
+
self.index_to_db[index] = db_file
|
| 93 |
|
| 94 |
self.logger.info(f"Found {len(org_files)} organism files")
|
| 95 |
except Exception as e:
|
|
|
|
| 98 |
return org_files
|
| 99 |
|
| 100 |
def get_shared_seeds(self, db_files, limit=False):
|
| 101 |
+
"""Get shared seeds between organisms"""
|
| 102 |
try:
|
| 103 |
+
self.logger.debug(f"Getting shared seeds for {len(db_files)} organisms")
|
| 104 |
|
| 105 |
+
# Create temporary database for join operations
|
| 106 |
+
temp_db_path = os.path.join(self.app_dir, "temp_join.db")
|
| 107 |
+
new_conn = sqlite3.connect(temp_db_path)
|
| 108 |
new_c = new_conn.cursor()
|
| 109 |
+
|
| 110 |
+
# Set pragmas for better performance
|
| 111 |
+
new_c.execute("PRAGMA synchronous = OFF")
|
| 112 |
+
new_c.execute("PRAGMA journal_mode = OFF")
|
| 113 |
+
new_c.execute("PRAGMA locking_mode = EXCLUSIVE")
|
| 114 |
+
|
| 115 |
+
# Clean up any existing tables
|
| 116 |
+
new_c.execute("DROP TABLE IF EXISTS repeats")
|
| 117 |
+
new_c.execute("DROP TABLE IF EXISTS join_results")
|
| 118 |
+
new_c.execute("VACUUM")
|
| 119 |
+
|
| 120 |
+
# Create results table
|
| 121 |
+
new_c.execute("CREATE TABLE join_results (seed TEXT)")
|
| 122 |
+
|
| 123 |
+
try:
|
| 124 |
+
# Attach all databases
|
| 125 |
+
for i, db_file in enumerate(db_files, 1):
|
| 126 |
+
new_c.execute(f"ATTACH DATABASE '{db_file}' AS main{i}")
|
| 127 |
+
|
| 128 |
+
# Start transaction
|
| 129 |
+
new_c.execute("BEGIN TRANSACTION")
|
| 130 |
+
|
| 131 |
+
# Build query to find seeds shared across all organisms
|
| 132 |
+
base_sql = """INSERT into main.join_results
|
| 133 |
+
select main1.repeats.seed from main1.repeats"""
|
| 134 |
+
|
| 135 |
+
joins = []
|
| 136 |
+
for i in range(2, len(db_files) + 1):
|
| 137 |
+
joins.append(
|
| 138 |
+
f"inner join main{i}.repeats on "
|
| 139 |
+
f"main{i-1}.repeats.seed = main{i}.repeats.seed"
|
| 140 |
+
)
|
| 141 |
+
|
| 142 |
+
full_sql = base_sql + " " + " ".join(joins)
|
| 143 |
+
self.logger.debug(f"Executing SQL for shared seeds: {full_sql}")
|
| 144 |
+
new_c.execute(full_sql)
|
| 145 |
+
|
| 146 |
+
# Get results based on limit parameter
|
| 147 |
+
if limit:
|
| 148 |
+
shared_seeds = new_c.execute("SELECT DISTINCT seed FROM join_results LIMIT 1000").fetchall()
|
| 149 |
+
result = [seed[0] for seed in shared_seeds]
|
| 150 |
+
else:
|
| 151 |
+
result = new_c.execute("SELECT COUNT(DISTINCT seed) FROM join_results").fetchone()[0]
|
| 152 |
+
|
| 153 |
+
# Commit and cleanup
|
| 154 |
+
new_c.execute("END TRANSACTION")
|
| 155 |
+
new_c.close()
|
| 156 |
+
new_conn.close()
|
| 157 |
+
|
| 158 |
+
try:
|
| 159 |
+
os.remove(temp_db_path)
|
| 160 |
+
except:
|
| 161 |
+
self.logger.warning("Could not remove temporary database file")
|
| 162 |
+
|
| 163 |
+
self.logger.debug(f"Found {len(result) if limit else result} shared seeds")
|
| 164 |
+
return result
|
| 165 |
+
|
| 166 |
+
except Exception as e:
|
| 167 |
+
new_c.execute("ROLLBACK")
|
| 168 |
+
raise e
|
| 169 |
+
|
| 170 |
except Exception as e:
|
| 171 |
+
self.logger.error(f"Error getting shared seeds: {str(e)}")
|
| 172 |
+
self.logger.exception("Full traceback:")
|
| 173 |
+
show_error(self.settings, "Error getting shared seeds", str(e))
|
| 174 |
return [] if limit else 0
|
| 175 |
|
| 176 |
def get_seed_data(self, seed, db_files):
|
|
|
|
| 193 |
return data
|
| 194 |
|
| 195 |
def get_heatmap_data(self, db_files):
|
| 196 |
+
"""Get data for heatmap visualization"""
|
| 197 |
try:
|
| 198 |
size = len(db_files)
|
| 199 |
arr = [[0 for _ in range(size)] for _ in range(size)]
|
| 200 |
|
| 201 |
+
# Get shared seeds between pairs of organisms
|
| 202 |
for i, j in itertools.combinations(range(size), 2):
|
| 203 |
+
# Create temporary database for join operations
|
| 204 |
+
temp_db_path = os.path.join(self.app_dir, "temp_join.db")
|
| 205 |
+
new_conn = sqlite3.connect(temp_db_path)
|
| 206 |
+
new_c = new_conn.cursor()
|
| 207 |
+
|
| 208 |
+
try:
|
| 209 |
+
# Attach databases
|
| 210 |
+
new_c.execute(f"ATTACH DATABASE '{db_files[i]}' AS main1")
|
| 211 |
+
new_c.execute(f"ATTACH DATABASE '{db_files[j]}' AS main2")
|
| 212 |
+
|
| 213 |
+
# Count shared seeds
|
| 214 |
+
sql = """SELECT COUNT(DISTINCT main1.repeats.seed)
|
| 215 |
+
FROM main1.repeats
|
| 216 |
+
INNER JOIN main2.repeats
|
| 217 |
+
ON main1.repeats.seed = main2.repeats.seed"""
|
| 218 |
+
|
| 219 |
+
shared_count = new_c.execute(sql).fetchone()[0]
|
| 220 |
+
arr[i][j] = arr[j][i] = shared_count
|
| 221 |
+
|
| 222 |
+
finally:
|
| 223 |
+
new_c.close()
|
| 224 |
+
new_conn.close()
|
| 225 |
+
try:
|
| 226 |
+
os.remove(temp_db_path)
|
| 227 |
+
except:
|
| 228 |
+
pass
|
| 229 |
|
| 230 |
+
# Get individual organism seed counts
|
| 231 |
for i in range(size):
|
| 232 |
with sqlite3.connect(db_files[i]) as conn:
|
| 233 |
c = conn.cursor()
|
| 234 |
arr[i][i] = c.execute("SELECT COUNT(*) FROM repeats").fetchone()[0]
|
| 235 |
|
| 236 |
return arr
|
| 237 |
+
|
| 238 |
except Exception as e:
|
| 239 |
+
self.logger.error(f"Error generating heatmap data: {str(e)}")
|
| 240 |
+
self.logger.exception("Full traceback:")
|
| 241 |
+
show_error(self.settings, "Error generating heatmap data", str(e))
|
| 242 |
return []
|
| 243 |
|
| 244 |
def get_seed_locations(self, seeds, db_files):
|
|
@@ -8,8 +8,8 @@ class StartupWindowModel(QObject):
|
|
| 8 |
self.settings = global_settings
|
| 9 |
self.logger = global_settings.get_logger()
|
| 10 |
|
| 11 |
-
# Connect to the
|
| 12 |
-
self.settings.
|
| 13 |
|
| 14 |
def get_db_path(self):
|
| 15 |
return self.settings.get_db_path()
|
|
|
|
| 8 |
self.settings = global_settings
|
| 9 |
self.logger = global_settings.get_logger()
|
| 10 |
|
| 11 |
+
# Connect to the DatabaseManager's signals instead of GlobalSettings
|
| 12 |
+
self.settings.db_manager.db_state_changed.connect(self.on_db_state_updated)
|
| 13 |
|
| 14 |
def get_db_path(self):
|
| 15 |
return self.settings.get_db_path()
|
|
@@ -9,18 +9,14 @@ import threading
|
|
| 9 |
from collections import defaultdict
|
| 10 |
import re
|
| 11 |
import traceback
|
| 12 |
-
import time
|
| 13 |
import logging
|
| 14 |
-
from multiprocessing import Pool
|
| 15 |
-
from functools import partial
|
| 16 |
-
from multiprocessing import cpu_count
|
| 17 |
|
| 18 |
class ViewTargetsModel(HomeWindowModel):
|
| 19 |
def __init__(self, global_settings):
|
| 20 |
super().__init__(global_settings)
|
| 21 |
-
self.
|
| 22 |
self.cspr_parser = None
|
| 23 |
-
self.annotation_parser = None
|
| 24 |
self.gene_sequence = ""
|
| 25 |
self.highlighted_sequence = ""
|
| 26 |
self.gene_info = {}
|
|
@@ -33,14 +29,12 @@ class ViewTargetsModel(HomeWindowModel):
|
|
| 33 |
self.extended_sequence = ""
|
| 34 |
self.chromosome = ""
|
| 35 |
|
| 36 |
-
# Cache structures
|
| 37 |
self._gene_data_cache = {}
|
| 38 |
self._sequence_cache = {}
|
| 39 |
self._parser_cache = {}
|
| 40 |
self._chromosome_seqs = {}
|
| 41 |
-
self.
|
| 42 |
|
| 43 |
-
# Connect to annotation file changes
|
| 44 |
self.global_settings.annotation_file_changed.connect(self._on_annotation_file_changed)
|
| 45 |
|
| 46 |
# Initialize annotation path
|
|
@@ -59,7 +53,6 @@ class ViewTargetsModel(HomeWindowModel):
|
|
| 59 |
self.global_settings.annotation_file_changed.disconnect(self._on_annotation_file_changed)
|
| 60 |
self.global_settings.logger.debug("ViewTargetsModel disconnected from annotation file changes")
|
| 61 |
|
| 62 |
-
# Clear caches
|
| 63 |
self._gene_data_cache.clear()
|
| 64 |
self._sequence_cache.clear()
|
| 65 |
self._parser_cache.clear()
|
|
@@ -90,19 +83,15 @@ class ViewTargetsModel(HomeWindowModel):
|
|
| 90 |
except Exception as e:
|
| 91 |
self.logger.error(f"Error in _on_annotation_file_changed: {str(e)}")
|
| 92 |
|
| 93 |
-
def
|
| 94 |
-
"""
|
| 95 |
-
total_start = time.time()
|
| 96 |
-
|
| 97 |
try:
|
| 98 |
-
self.logger.debug(f"Starting
|
| 99 |
|
| 100 |
-
# Store organism and endonuclease for potential reloading
|
| 101 |
self.organism = organism
|
| 102 |
self.endonuclease = endonuclease
|
| 103 |
|
| 104 |
# Get CSPR parser from cache or create new one
|
| 105 |
-
parser_start = time.time()
|
| 106 |
cspr_key = f"{organism}_{endonuclease}"
|
| 107 |
if cspr_key in self._parser_cache:
|
| 108 |
self.cspr_parser = self._parser_cache[cspr_key]
|
|
@@ -118,64 +107,77 @@ class ViewTargetsModel(HomeWindowModel):
|
|
| 118 |
self.cspr_parser = CSPRparser(cspr_path, self.global_settings.get_casper_info_path())
|
| 119 |
self._parser_cache[cspr_key] = self.cspr_parser
|
| 120 |
self.logger.debug("Created new CSPR parser")
|
| 121 |
-
parser_time = time.time() - parser_start
|
| 122 |
-
self.logger.debug(f"CSPR parser initialization time: {parser_time:.2f} seconds")
|
| 123 |
|
| 124 |
-
# Initialize
|
| 125 |
-
|
| 126 |
-
self.targets = []
|
| 127 |
self.available_genes = set()
|
| 128 |
-
init_time = time.time() - init_start
|
| 129 |
-
self.logger.debug(f"Initialization time: {init_time:.2f} seconds")
|
| 130 |
|
| 131 |
-
#
|
| 132 |
-
|
| 133 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
for target in selected_targets:
|
| 135 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
start, end = map(int, target['location'].split('-'))
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
|
| 148 |
-
# Process
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
# Add feature_id to each result
|
| 155 |
for result in results:
|
| 156 |
-
#
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
self.logger.debug(f"Total target processing time: {process_time:.2f} seconds")
|
| 168 |
|
| 169 |
-
|
| 170 |
-
self.
|
| 171 |
-
|
|
|
|
| 172 |
|
| 173 |
except Exception as e:
|
| 174 |
-
self.logger.error(f"Error in
|
| 175 |
self.logger.error(f"Stack trace: {traceback.format_exc()}")
|
| 176 |
|
| 177 |
def _get_chromosome_sequence(self, chromosome):
|
| 178 |
-
"""Get chromosome sequence on demand"""
|
| 179 |
if not hasattr(self, '_chromosome_seqs'):
|
| 180 |
self._chromosome_seqs = {}
|
| 181 |
|
|
@@ -234,9 +236,9 @@ class ViewTargetsModel(HomeWindowModel):
|
|
| 234 |
self.logger.error(f"Stack trace: {traceback.format_exc()}")
|
| 235 |
return None
|
| 236 |
|
| 237 |
-
def
|
| 238 |
-
"""Return all
|
| 239 |
-
return self.
|
| 240 |
|
| 241 |
def get_available_genes(self):
|
| 242 |
"""Get list of available genes with format 'feature_id: feature_name'"""
|
|
@@ -250,78 +252,169 @@ class ViewTargetsModel(HomeWindowModel):
|
|
| 250 |
self.logger.error(f"Error getting available genes: {str(e)}")
|
| 251 |
return []
|
| 252 |
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
def _process_target(self, target):
|
| 256 |
-
"""Process a single target - moved to separate method for parallel processing"""
|
| 257 |
try:
|
| 258 |
-
# Your existing
|
| 259 |
# Make sure to handle any shared resources thread-safely
|
| 260 |
pass
|
| 261 |
except Exception as e:
|
| 262 |
-
logging.error(f"Error processing
|
| 263 |
return None
|
| 264 |
|
| 265 |
-
def get_gene_sequence(self,
|
| 266 |
"""Get gene sequence with optimized caching and minimal I/O"""
|
| 267 |
try:
|
|
|
|
| 268 |
# Check sequence cache first
|
| 269 |
-
cache_key = f"{
|
| 270 |
if cache_key in self._sequence_cache:
|
| 271 |
-
self.logger.debug(f"Cache hit for
|
| 272 |
return self._sequence_cache[cache_key]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 273 |
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
return None
|
| 280 |
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 285 |
return None
|
| 286 |
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 295 |
|
| 296 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 297 |
padding = 30
|
| 298 |
-
seq_start = max(0, start - padding)
|
| 299 |
-
seq_end = min(len(sequence), end + padding)
|
| 300 |
|
| 301 |
-
#
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 305 |
|
| 306 |
-
|
| 307 |
|
| 308 |
-
|
| 309 |
-
result = {
|
| 310 |
-
'sequence': full_sequence,
|
| 311 |
-
'chrom_length': len(sequence),
|
| 312 |
-
'start': start,
|
| 313 |
-
'end': end,
|
| 314 |
-
'padded_start': seq_start,
|
| 315 |
-
'padded_end': seq_end
|
| 316 |
-
}
|
| 317 |
-
self._sequence_cache[cache_key] = result
|
| 318 |
-
|
| 319 |
-
self.logger.debug(f"Retrieved and cached sequence for locus tag {locus_tag} ({len(full_sequence)} bp)")
|
| 320 |
-
return result
|
| 321 |
|
|
|
|
|
|
|
| 322 |
except Exception as e:
|
| 323 |
-
self.logger.error(f"Error getting
|
| 324 |
-
self.logger.error(f"Stack trace: {traceback.format_exc()}")
|
| 325 |
return None
|
| 326 |
|
| 327 |
def get_scoring_options(self):
|
|
@@ -347,4 +440,33 @@ class ViewTargetsModel(HomeWindowModel):
|
|
| 347 |
self.logger.debug(f"Updated scoring options: {options}")
|
| 348 |
|
| 349 |
except Exception as e:
|
| 350 |
-
self.logger.error(f"Error setting scoring options: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
from collections import defaultdict
|
| 10 |
import re
|
| 11 |
import traceback
|
|
|
|
| 12 |
import logging
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
class ViewTargetsModel(HomeWindowModel):
|
| 15 |
def __init__(self, global_settings):
|
| 16 |
super().__init__(global_settings)
|
| 17 |
+
self.guides = []
|
| 18 |
self.cspr_parser = None
|
| 19 |
+
self.annotation_parser = None
|
| 20 |
self.gene_sequence = ""
|
| 21 |
self.highlighted_sequence = ""
|
| 22 |
self.gene_info = {}
|
|
|
|
| 29 |
self.extended_sequence = ""
|
| 30 |
self.chromosome = ""
|
| 31 |
|
|
|
|
| 32 |
self._gene_data_cache = {}
|
| 33 |
self._sequence_cache = {}
|
| 34 |
self._parser_cache = {}
|
| 35 |
self._chromosome_seqs = {}
|
| 36 |
+
self._cached_guides = {}
|
| 37 |
|
|
|
|
| 38 |
self.global_settings.annotation_file_changed.connect(self._on_annotation_file_changed)
|
| 39 |
|
| 40 |
# Initialize annotation path
|
|
|
|
| 53 |
self.global_settings.annotation_file_changed.disconnect(self._on_annotation_file_changed)
|
| 54 |
self.global_settings.logger.debug("ViewTargetsModel disconnected from annotation file changes")
|
| 55 |
|
|
|
|
| 56 |
self._gene_data_cache.clear()
|
| 57 |
self._sequence_cache.clear()
|
| 58 |
self._parser_cache.clear()
|
|
|
|
| 83 |
except Exception as e:
|
| 84 |
self.logger.error(f"Error in _on_annotation_file_changed: {str(e)}")
|
| 85 |
|
| 86 |
+
def load_guides(self, selected_targets, organism, endonuclease):
|
| 87 |
+
"""Load guides with proper error handling"""
|
|
|
|
|
|
|
| 88 |
try:
|
| 89 |
+
self.logger.debug(f"Starting load_guides with {len(selected_targets)} targets")
|
| 90 |
|
|
|
|
| 91 |
self.organism = organism
|
| 92 |
self.endonuclease = endonuclease
|
| 93 |
|
| 94 |
# Get CSPR parser from cache or create new one
|
|
|
|
| 95 |
cspr_key = f"{organism}_{endonuclease}"
|
| 96 |
if cspr_key in self._parser_cache:
|
| 97 |
self.cspr_parser = self._parser_cache[cspr_key]
|
|
|
|
| 107 |
self.cspr_parser = CSPRparser(cspr_path, self.global_settings.get_casper_info_path())
|
| 108 |
self._parser_cache[cspr_key] = self.cspr_parser
|
| 109 |
self.logger.debug("Created new CSPR parser")
|
|
|
|
|
|
|
| 110 |
|
| 111 |
+
# Initialize guides and genes
|
| 112 |
+
self.guides = []
|
|
|
|
| 113 |
self.available_genes = set()
|
|
|
|
|
|
|
| 114 |
|
| 115 |
+
# Use a set to track unique guide positions
|
| 116 |
+
seen_guides = set()
|
| 117 |
+
|
| 118 |
+
# Create chromosome mapping by counting carets
|
| 119 |
+
chrom_mapping = {}
|
| 120 |
+
chrom_count = 0
|
| 121 |
+
annotation_file = self.global_settings.get_current_annotation_file()
|
| 122 |
+
annotation_path = os.path.join(self.global_settings.get_db_path(), 'GBFF', annotation_file)
|
| 123 |
+
|
| 124 |
+
for record in SeqIO.parse(annotation_path, "genbank"):
|
| 125 |
+
chrom_count += 1
|
| 126 |
+
chrom_mapping[record.id] = str(chrom_count)
|
| 127 |
+
|
| 128 |
+
batch_guides = defaultdict(list)
|
| 129 |
for target in selected_targets:
|
| 130 |
+
# Get chromosome number from mapping if full_chromosome is available
|
| 131 |
+
if 'full_chromosome' in target:
|
| 132 |
+
chrom = chrom_mapping.get(target['full_chromosome'], target['chromosome'])
|
| 133 |
+
else:
|
| 134 |
+
chrom = target['chromosome']
|
| 135 |
+
|
| 136 |
start, end = map(int, target['location'].split('-'))
|
| 137 |
+
|
| 138 |
+
# Create a unique identifier for this position range
|
| 139 |
+
position_key = f"{chrom}:{start}-{end}"
|
| 140 |
+
|
| 141 |
+
# Only add if we haven't seen this position range before
|
| 142 |
+
if position_key not in seen_guides:
|
| 143 |
+
seen_guides.add(position_key)
|
| 144 |
+
batch_guides[chrom].append({
|
| 145 |
+
'feature_name': target['feature_name'],
|
| 146 |
+
'feature_id': target['feature_id'],
|
| 147 |
+
'start': start,
|
| 148 |
+
'end': end
|
| 149 |
+
})
|
| 150 |
+
self.available_genes.add((target['feature_id'], target['feature_name']))
|
| 151 |
|
| 152 |
+
# Process guides by chromosome
|
| 153 |
+
unique_guides = {} # Use dict to track unique guides by sequence
|
| 154 |
+
for chrom, guides in batch_guides.items():
|
| 155 |
+
results = self.cspr_parser.read_targets_batch(chrom, guides, endonuclease)
|
| 156 |
+
|
| 157 |
+
# Add feature_id to each result and deduplicate
|
|
|
|
| 158 |
for result in results:
|
| 159 |
+
# Create a unique key for each guide
|
| 160 |
+
guide_key = (result['sequence'], result['position'], result['strand'])
|
| 161 |
+
|
| 162 |
+
if guide_key not in unique_guides:
|
| 163 |
+
# Find matching guide to get feature_id
|
| 164 |
+
for target in selected_targets:
|
| 165 |
+
if (target['start'] <= result['position'] <= target['end'] and
|
| 166 |
+
target['feature_name'] == result['feature_name']):
|
| 167 |
+
result['feature_id'] = target['feature_id']
|
| 168 |
+
unique_guides[guide_key] = result
|
| 169 |
+
break
|
|
|
|
| 170 |
|
| 171 |
+
# Convert unique guides back to list
|
| 172 |
+
self.guides = list(unique_guides.values())
|
| 173 |
+
|
| 174 |
+
self.logger.debug(f"Found {len(self.guides)} unique guides")
|
| 175 |
|
| 176 |
except Exception as e:
|
| 177 |
+
self.logger.error(f"Error in load_guides: {str(e)}")
|
| 178 |
self.logger.error(f"Stack trace: {traceback.format_exc()}")
|
| 179 |
|
| 180 |
def _get_chromosome_sequence(self, chromosome):
|
|
|
|
| 181 |
if not hasattr(self, '_chromosome_seqs'):
|
| 182 |
self._chromosome_seqs = {}
|
| 183 |
|
|
|
|
| 236 |
self.logger.error(f"Stack trace: {traceback.format_exc()}")
|
| 237 |
return None
|
| 238 |
|
| 239 |
+
def get_guides(self):
|
| 240 |
+
"""Return all guides with their feature IDs"""
|
| 241 |
+
return self.guides
|
| 242 |
|
| 243 |
def get_available_genes(self):
|
| 244 |
"""Get list of available genes with format 'feature_id: feature_name'"""
|
|
|
|
| 252 |
self.logger.error(f"Error getting available genes: {str(e)}")
|
| 253 |
return []
|
| 254 |
|
| 255 |
+
def _process_guide(self, guide):
|
| 256 |
+
"""Process a single guide - moved to separate method for parallel processing"""
|
|
|
|
|
|
|
| 257 |
try:
|
| 258 |
+
# Your existing guide processing logic here
|
| 259 |
# Make sure to handle any shared resources thread-safely
|
| 260 |
pass
|
| 261 |
except Exception as e:
|
| 262 |
+
logging.error(f"Error processing guide: {e}")
|
| 263 |
return None
|
| 264 |
|
| 265 |
+
def get_gene_sequence(self, identifier):
|
| 266 |
"""Get gene sequence with optimized caching and minimal I/O"""
|
| 267 |
try:
|
| 268 |
+
print(f"Getting gene sequence for identifier: {identifier}")
|
| 269 |
# Check sequence cache first
|
| 270 |
+
cache_key = f"{identifier}_sequence"
|
| 271 |
if cache_key in self._sequence_cache:
|
| 272 |
+
self.logger.debug(f"Cache hit for sequence: {identifier}")
|
| 273 |
return self._sequence_cache[cache_key]
|
| 274 |
+
|
| 275 |
+
# Check if this is a position-based search
|
| 276 |
+
if "chrom" in identifier and "start:" in identifier:
|
| 277 |
+
try:
|
| 278 |
+
# Parse position from the text (format: "chrom X, start: Y, end: Z")
|
| 279 |
+
parts = identifier.split(',')
|
| 280 |
+
chrom = int(parts[0].split('chrom')[1].strip())
|
| 281 |
+
start = int(parts[1].split('start:')[1].strip())
|
| 282 |
+
end = int(parts[2].split('end:')[1].strip())
|
| 283 |
+
|
| 284 |
+
# Get sequence directly using _get_sequence_for_position
|
| 285 |
+
sequence = self._get_sequence_for_position(chrom, start, end)
|
| 286 |
+
if sequence:
|
| 287 |
+
result = {
|
| 288 |
+
'sequence': sequence,
|
| 289 |
+
'start': start,
|
| 290 |
+
'end': end,
|
| 291 |
+
'chrom_length': len(sequence)
|
| 292 |
+
}
|
| 293 |
+
self._sequence_cache[cache_key] = result
|
| 294 |
+
self.logger.debug(f"Retrieved and cached position sequence ({len(sequence)} bp)")
|
| 295 |
+
return result
|
| 296 |
+
|
| 297 |
+
self.logger.warning(f"No sequence found for position {chrom}:{start}-{end}")
|
| 298 |
+
return None
|
| 299 |
+
|
| 300 |
+
except Exception as e:
|
| 301 |
+
self.logger.error(f"Error parsing position or getting sequence: {str(e)}")
|
| 302 |
+
return None
|
| 303 |
+
else:
|
| 304 |
+
# Regular gene-based search
|
| 305 |
+
self.logger.debug(f"Getting gene data for locus tag: {identifier}")
|
| 306 |
+
gene_data = self.get_gene_data(identifier)
|
| 307 |
+
if not gene_data or 'info' not in gene_data:
|
| 308 |
+
self.logger.warning(f"No gene data found for locus tag: {identifier}")
|
| 309 |
+
return None
|
| 310 |
|
| 311 |
+
# Parse location string (format: "start:end(strand)")
|
| 312 |
+
location = gene_data['info']['location']
|
| 313 |
+
if ':' not in location:
|
| 314 |
+
self.logger.warning(f"Invalid location format: {location}")
|
| 315 |
+
return None
|
|
|
|
| 316 |
|
| 317 |
+
# Extract start and end positions
|
| 318 |
+
start = int(location.split(':')[0])
|
| 319 |
+
end = int(location.split(':')[1].split('(')[0])
|
| 320 |
+
|
| 321 |
+
# Get sequence from gene_data directly if available
|
| 322 |
+
if 'sequence' in gene_data:
|
| 323 |
+
sequence = gene_data['sequence']
|
| 324 |
+
self.logger.debug(f"Got sequence of length: {len(sequence)}")
|
| 325 |
+
|
| 326 |
+
# Format sequence with padding in lowercase
|
| 327 |
+
padding = 30
|
| 328 |
+
padded_start = max(0, start - padding)
|
| 329 |
+
padded_end = min(len(sequence), end + padding)
|
| 330 |
+
|
| 331 |
+
# Split sequence into parts
|
| 332 |
+
five_prime_pad = sequence[:start - padded_start].lower() if start > padded_start else ""
|
| 333 |
+
main_sequence = sequence[start - padded_start:end - padded_start].upper()
|
| 334 |
+
three_prime_pad = sequence[end - padded_start:].lower()
|
| 335 |
+
|
| 336 |
+
# Combine parts
|
| 337 |
+
formatted_sequence = five_prime_pad + main_sequence + three_prime_pad
|
| 338 |
+
|
| 339 |
+
# Cache the result
|
| 340 |
+
result = {
|
| 341 |
+
'sequence': formatted_sequence,
|
| 342 |
+
'chrom_length': len(sequence),
|
| 343 |
+
'start': start,
|
| 344 |
+
'end': end,
|
| 345 |
+
'padded_start': padded_start,
|
| 346 |
+
'padded_end': padded_end
|
| 347 |
+
}
|
| 348 |
+
self._sequence_cache[cache_key] = result
|
| 349 |
+
|
| 350 |
+
self.logger.debug(f"Retrieved and cached sequence for locus tag {identifier} ({len(formatted_sequence)} bp)")
|
| 351 |
+
return result
|
| 352 |
+
|
| 353 |
+
self.logger.warning(f"No sequence data found in gene_data for {identifier}")
|
| 354 |
return None
|
| 355 |
|
| 356 |
+
except Exception as e:
|
| 357 |
+
self.logger.error(f"Error getting gene sequence: {str(e)}")
|
| 358 |
+
self.logger.error(f"Stack trace: {traceback.format_exc()}")
|
| 359 |
+
return None
|
| 360 |
+
|
| 361 |
+
def _get_sequence_for_position(self, chrom, start, end):
|
| 362 |
+
"""Get sequence for a given position with proper padding handling"""
|
| 363 |
+
try:
|
| 364 |
+
if not hasattr(self, 'annotation_parser') or self.annotation_parser is None:
|
| 365 |
+
self.annotation_parser = AnnotationParser(self.global_settings)
|
| 366 |
+
annotation_file = self.global_settings.get_current_annotation_file()
|
| 367 |
+
annotation_path = os.path.join(self.global_settings.get_db_path(), 'GBFF', annotation_file)
|
| 368 |
+
self.annotation_parser.set_annotation_file(annotation_path)
|
| 369 |
|
| 370 |
+
# Get the full chromosome ID by counting carets in annotation file
|
| 371 |
+
full_chrom = None
|
| 372 |
+
chrom_count = 0
|
| 373 |
+
|
| 374 |
+
try:
|
| 375 |
+
for record in SeqIO.parse(self.annotation_path, "genbank"):
|
| 376 |
+
chrom_count += 1
|
| 377 |
+
if chrom_count == int(chrom): # Match based on position rather than ID number
|
| 378 |
+
full_chrom = record.id
|
| 379 |
+
self.logger.debug(f"Found chromosome {chrom} as {full_chrom}")
|
| 380 |
+
break
|
| 381 |
+
except Exception as e:
|
| 382 |
+
self.logger.error(f"Error finding chromosome by position: {str(e)}")
|
| 383 |
+
return None
|
| 384 |
+
|
| 385 |
+
if not full_chrom:
|
| 386 |
+
self.logger.warning(f"Could not find chromosome at position {chrom}")
|
| 387 |
+
return None
|
| 388 |
+
|
| 389 |
+
feature_info = {
|
| 390 |
+
'chromosome': full_chrom,
|
| 391 |
+
'start': start-1,
|
| 392 |
+
'end': end
|
| 393 |
+
}
|
| 394 |
+
|
| 395 |
+
self.logger.debug(f"Getting sequence for feature info: {feature_info}")
|
| 396 |
+
|
| 397 |
+
sequence = self.annotation_parser._get_sequence_for_gene(feature_info)
|
| 398 |
+
if sequence:
|
| 399 |
padding = 30
|
|
|
|
|
|
|
| 400 |
|
| 401 |
+
# Handle start position padding
|
| 402 |
+
if start == 1:
|
| 403 |
+
# No padding at start if starting at position 1
|
| 404 |
+
five_prime_pad = ""
|
| 405 |
+
main_sequence = sequence[:-(padding if len(sequence) > padding else 0)].upper()
|
| 406 |
+
else:
|
| 407 |
+
five_prime_pad = sequence[:padding].lower() if len(sequence) > padding else ""
|
| 408 |
+
main_sequence = sequence[padding:-padding].upper() if len(sequence) > 60 else sequence.upper()
|
| 409 |
|
| 410 |
+
three_prime_pad = sequence[-padding:].lower() if len(sequence) > padding else ""
|
| 411 |
|
| 412 |
+
return five_prime_pad + main_sequence + three_prime_pad
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 413 |
|
| 414 |
+
return None
|
| 415 |
+
|
| 416 |
except Exception as e:
|
| 417 |
+
self.logger.error(f"Error getting sequence for position: {str(e)}")
|
|
|
|
| 418 |
return None
|
| 419 |
|
| 420 |
def get_scoring_options(self):
|
|
|
|
| 440 |
self.logger.debug(f"Updated scoring options: {options}")
|
| 441 |
|
| 442 |
except Exception as e:
|
| 443 |
+
self.logger.error(f"Error setting scoring options: {str(e)}")
|
| 444 |
+
|
| 445 |
+
def get_gene_sequence_for_range(self, identifier, start, end):
|
| 446 |
+
try:
|
| 447 |
+
# For feature-based searches
|
| 448 |
+
gene_data = self.get_gene_data(identifier)
|
| 449 |
+
if not gene_data or 'info' not in gene_data:
|
| 450 |
+
self.logger.warning(f"No gene data found for identifier: {identifier}")
|
| 451 |
+
return None
|
| 452 |
+
|
| 453 |
+
# Get chromosome from gene data
|
| 454 |
+
chrom = gene_data['info']['chromosome'].split('.')[-1] # Extract chromosome number
|
| 455 |
+
|
| 456 |
+
# Use _get_sequence_for_position to get sequence with padding
|
| 457 |
+
sequence = self._get_sequence_for_position(int(chrom), start, end)
|
| 458 |
+
if sequence:
|
| 459 |
+
result = {
|
| 460 |
+
'sequence': sequence,
|
| 461 |
+
'start': start,
|
| 462 |
+
'end': end
|
| 463 |
+
}
|
| 464 |
+
return result
|
| 465 |
+
|
| 466 |
+
self.logger.warning(f"No sequence found for range {start}-{end} in chromosome {chrom}")
|
| 467 |
+
return None
|
| 468 |
+
|
| 469 |
+
except Exception as e:
|
| 470 |
+
self.logger.error(f"Error getting gene sequence for range: {str(e)}")
|
| 471 |
+
self.logger.error(f"Stack trace: {traceback.format_exc()}")
|
| 472 |
+
return None
|
|
@@ -20,26 +20,27 @@
|
|
| 20 |
</property>
|
| 21 |
<widget class="QWidget" name="centralwidget">
|
| 22 |
<layout class="QGridLayout" name="gridLayout_2">
|
| 23 |
-
<item row="
|
| 24 |
-
<spacer name="horizontalSpacer">
|
| 25 |
-
<property name="orientation">
|
| 26 |
-
<enum>Qt::Horizontal</enum>
|
| 27 |
-
</property>
|
| 28 |
-
<property name="sizeType">
|
| 29 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 30 |
-
</property>
|
| 31 |
-
<property name="sizeHint" stdset="0">
|
| 32 |
-
<size>
|
| 33 |
-
<width>20</width>
|
| 34 |
-
<height>20</height>
|
| 35 |
-
</size>
|
| 36 |
-
</property>
|
| 37 |
-
</spacer>
|
| 38 |
-
</item>
|
| 39 |
-
<item row="1" column="1">
|
| 40 |
<layout class="QGridLayout" name="gridLayout">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
<item row="2" column="0">
|
| 42 |
-
<widget class="QLabel" name="
|
| 43 |
<property name="sizePolicy">
|
| 44 |
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
| 45 |
<horstretch>0</horstretch>
|
|
@@ -51,15 +52,8 @@
|
|
| 51 |
</property>
|
| 52 |
</widget>
|
| 53 |
</item>
|
| 54 |
-
<item row="1" column="0" colspan="2">
|
| 55 |
-
<widget class="Line" name="line">
|
| 56 |
-
<property name="orientation">
|
| 57 |
-
<enum>Qt::Horizontal</enum>
|
| 58 |
-
</property>
|
| 59 |
-
</widget>
|
| 60 |
-
</item>
|
| 61 |
<item row="4" column="0" alignment="Qt::AlignLeft">
|
| 62 |
-
<widget class="QPushButton" name="
|
| 63 |
<property name="minimumSize">
|
| 64 |
<size>
|
| 65 |
<width>125</width>
|
|
@@ -75,31 +69,14 @@
|
|
| 75 |
</widget>
|
| 76 |
</item>
|
| 77 |
<item row="0" column="0">
|
| 78 |
-
<widget class="QLabel" name="
|
| 79 |
<property name="text">
|
| 80 |
<string>Co-Targeting</string>
|
| 81 |
</property>
|
| 82 |
</widget>
|
| 83 |
</item>
|
| 84 |
-
<item row="3" column="0" colspan="2">
|
| 85 |
-
<widget class="QTableWidget" name="endo_table">
|
| 86 |
-
<property name="toolTip">
|
| 87 |
-
<string><html><head/><body><p><span style=" font-size:8pt;">These are the list of endonucleases available for this organism. </span></p><p><span style=" font-size:8pt;">Select which endonucleases you want shown at the same time.</span></p></body></html></string>
|
| 88 |
-
</property>
|
| 89 |
-
</widget>
|
| 90 |
-
</item>
|
| 91 |
-
<item row="2" column="1">
|
| 92 |
-
<widget class="QLineEdit" name="orgName">
|
| 93 |
-
<property name="enabled">
|
| 94 |
-
<bool>false</bool>
|
| 95 |
-
</property>
|
| 96 |
-
<property name="toolTip">
|
| 97 |
-
<string><html><head/><body><p>This is the organism that targets have been searched for.</p></body></html></string>
|
| 98 |
-
</property>
|
| 99 |
-
</widget>
|
| 100 |
-
</item>
|
| 101 |
<item row="4" column="1" alignment="Qt::AlignRight">
|
| 102 |
-
<widget class="QPushButton" name="
|
| 103 |
<property name="minimumSize">
|
| 104 |
<size>
|
| 105 |
<width>125</width>
|
|
@@ -114,59 +91,17 @@
|
|
| 114 |
</property>
|
| 115 |
</widget>
|
| 116 |
</item>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
</layout>
|
| 118 |
</item>
|
| 119 |
-
<item row="1" column="2">
|
| 120 |
-
<spacer name="horizontalSpacer_2">
|
| 121 |
-
<property name="orientation">
|
| 122 |
-
<enum>Qt::Horizontal</enum>
|
| 123 |
-
</property>
|
| 124 |
-
<property name="sizeType">
|
| 125 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 126 |
-
</property>
|
| 127 |
-
<property name="sizeHint" stdset="0">
|
| 128 |
-
<size>
|
| 129 |
-
<width>20</width>
|
| 130 |
-
<height>20</height>
|
| 131 |
-
</size>
|
| 132 |
-
</property>
|
| 133 |
-
</spacer>
|
| 134 |
-
</item>
|
| 135 |
-
<item row="2" column="1">
|
| 136 |
-
<spacer name="verticalSpacer">
|
| 137 |
-
<property name="orientation">
|
| 138 |
-
<enum>Qt::Vertical</enum>
|
| 139 |
-
</property>
|
| 140 |
-
<property name="sizeType">
|
| 141 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 142 |
-
</property>
|
| 143 |
-
<property name="sizeHint" stdset="0">
|
| 144 |
-
<size>
|
| 145 |
-
<width>20</width>
|
| 146 |
-
<height>20</height>
|
| 147 |
-
</size>
|
| 148 |
-
</property>
|
| 149 |
-
</spacer>
|
| 150 |
-
</item>
|
| 151 |
-
<item row="0" column="1">
|
| 152 |
-
<spacer name="verticalSpacer_2">
|
| 153 |
-
<property name="orientation">
|
| 154 |
-
<enum>Qt::Vertical</enum>
|
| 155 |
-
</property>
|
| 156 |
-
<property name="sizeType">
|
| 157 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 158 |
-
</property>
|
| 159 |
-
<property name="sizeHint" stdset="0">
|
| 160 |
-
<size>
|
| 161 |
-
<width>20</width>
|
| 162 |
-
<height>20</height>
|
| 163 |
-
</size>
|
| 164 |
-
</property>
|
| 165 |
-
</spacer>
|
| 166 |
-
</item>
|
| 167 |
</layout>
|
| 168 |
</widget>
|
| 169 |
-
<widget class="QStatusBar" name="statusbar"/>
|
| 170 |
</widget>
|
| 171 |
<resources/>
|
| 172 |
<connections/>
|
|
|
|
| 20 |
</property>
|
| 21 |
<widget class="QWidget" name="centralwidget">
|
| 22 |
<layout class="QGridLayout" name="gridLayout_2">
|
| 23 |
+
<item row="0" column="0">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
<layout class="QGridLayout" name="gridLayout">
|
| 25 |
+
<item row="3" column="0" colspan="2">
|
| 26 |
+
<widget class="QTableWidget" name="tblEndonucleases">
|
| 27 |
+
<property name="toolTip">
|
| 28 |
+
<string><html><head/><body><p><span style=" font-size:8pt;">These are the list of endonucleases available for this organism. </span></p><p><span style=" font-size:8pt;">Select which endonucleases you want shown at the same time.</span></p></body></html></string>
|
| 29 |
+
</property>
|
| 30 |
+
</widget>
|
| 31 |
+
</item>
|
| 32 |
+
<item row="2" column="1">
|
| 33 |
+
<widget class="QLineEdit" name="ledOrganism">
|
| 34 |
+
<property name="enabled">
|
| 35 |
+
<bool>false</bool>
|
| 36 |
+
</property>
|
| 37 |
+
<property name="toolTip">
|
| 38 |
+
<string><html><head/><body><p>This is the organism that targets have been searched for.</p></body></html></string>
|
| 39 |
+
</property>
|
| 40 |
+
</widget>
|
| 41 |
+
</item>
|
| 42 |
<item row="2" column="0">
|
| 43 |
+
<widget class="QLabel" name="lblOrganism">
|
| 44 |
<property name="sizePolicy">
|
| 45 |
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
| 46 |
<horstretch>0</horstretch>
|
|
|
|
| 52 |
</property>
|
| 53 |
</widget>
|
| 54 |
</item>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
<item row="4" column="0" alignment="Qt::AlignLeft">
|
| 56 |
+
<widget class="QPushButton" name="pbtnCancel">
|
| 57 |
<property name="minimumSize">
|
| 58 |
<size>
|
| 59 |
<width>125</width>
|
|
|
|
| 69 |
</widget>
|
| 70 |
</item>
|
| 71 |
<item row="0" column="0">
|
| 72 |
+
<widget class="QLabel" name="lblTitle">
|
| 73 |
<property name="text">
|
| 74 |
<string>Co-Targeting</string>
|
| 75 |
</property>
|
| 76 |
</widget>
|
| 77 |
</item>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
<item row="4" column="1" alignment="Qt::AlignRight">
|
| 79 |
+
<widget class="QPushButton" name="pbtnSubmit">
|
| 80 |
<property name="minimumSize">
|
| 81 |
<size>
|
| 82 |
<width>125</width>
|
|
|
|
| 91 |
</property>
|
| 92 |
</widget>
|
| 93 |
</item>
|
| 94 |
+
<item row="1" column="0" colspan="2">
|
| 95 |
+
<widget class="Line" name="line">
|
| 96 |
+
<property name="orientation">
|
| 97 |
+
<enum>Qt::Horizontal</enum>
|
| 98 |
+
</property>
|
| 99 |
+
</widget>
|
| 100 |
+
</item>
|
| 101 |
</layout>
|
| 102 |
</item>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
</layout>
|
| 104 |
</widget>
|
|
|
|
| 105 |
</widget>
|
| 106 |
<resources/>
|
| 107 |
<connections/>
|
|
@@ -6,8 +6,8 @@
|
|
| 6 |
<rect>
|
| 7 |
<x>0</x>
|
| 8 |
<y>0</y>
|
| 9 |
-
<width>
|
| 10 |
-
<height>
|
| 11 |
</rect>
|
| 12 |
</property>
|
| 13 |
<property name="font">
|
|
@@ -20,35 +20,35 @@
|
|
| 20 |
</property>
|
| 21 |
<widget class="QWidget" name="centralwidget">
|
| 22 |
<layout class="QGridLayout" name="gridLayout_2">
|
| 23 |
-
<item row="
|
| 24 |
-
<widget class="QGroupBox" name="
|
| 25 |
<property name="title">
|
| 26 |
<string>Guide Options</string>
|
| 27 |
</property>
|
| 28 |
<layout class="QGridLayout" name="gridLayout_6">
|
| 29 |
<item row="1" column="0">
|
| 30 |
-
<widget class="QLabel" name="
|
| 31 |
<property name="text">
|
| 32 |
<string>5' Leading Sequence:</string>
|
| 33 |
</property>
|
| 34 |
</widget>
|
| 35 |
</item>
|
| 36 |
<item row="1" column="1">
|
| 37 |
-
<widget class="QLineEdit" name="
|
| 38 |
<property name="placeholderText">
|
| 39 |
<string>E.g. promoter, tRNA, homology overhang, etc.</string>
|
| 40 |
</property>
|
| 41 |
</widget>
|
| 42 |
</item>
|
| 43 |
<item row="2" column="1">
|
| 44 |
-
<widget class="QLineEdit" name="
|
| 45 |
<property name="placeholderText">
|
| 46 |
<string>E.g. gRNA scaffold, terminator, etc.</string>
|
| 47 |
</property>
|
| 48 |
</widget>
|
| 49 |
</item>
|
| 50 |
<item row="2" column="0">
|
| 51 |
-
<widget class="QLabel" name="
|
| 52 |
<property name="text">
|
| 53 |
<string>3' Trailing Sequence:</string>
|
| 54 |
</property>
|
|
@@ -57,21 +57,21 @@
|
|
| 57 |
</layout>
|
| 58 |
</widget>
|
| 59 |
</item>
|
| 60 |
-
<item row="
|
| 61 |
-
<widget class="QGroupBox" name="
|
| 62 |
<property name="title">
|
| 63 |
<string>Export Options</string>
|
| 64 |
</property>
|
| 65 |
<layout class="QGridLayout" name="gridLayout_3">
|
| 66 |
<item row="0" column="0">
|
| 67 |
-
<widget class="QLabel" name="
|
| 68 |
<property name="text">
|
| 69 |
-
<string>File
|
| 70 |
</property>
|
| 71 |
</widget>
|
| 72 |
</item>
|
| 73 |
<item row="1" column="0">
|
| 74 |
-
<widget class="QLabel" name="
|
| 75 |
<property name="sizePolicy">
|
| 76 |
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
| 77 |
<horstretch>0</horstretch>
|
|
@@ -84,14 +84,14 @@
|
|
| 84 |
</widget>
|
| 85 |
</item>
|
| 86 |
<item row="1" column="2">
|
| 87 |
-
<widget class="QLabel" name="
|
| 88 |
<property name="text">
|
| 89 |
<string>Delimiter:</string>
|
| 90 |
</property>
|
| 91 |
</widget>
|
| 92 |
</item>
|
| 93 |
<item row="1" column="1">
|
| 94 |
-
<widget class="QLineEdit" name="
|
| 95 |
<property name="toolTip">
|
| 96 |
<string><html><head/><body><p>Please write the file name you want.</p><p>Note: it is not necessary to add the file extension.</p></body></html></string>
|
| 97 |
</property>
|
|
@@ -101,7 +101,7 @@
|
|
| 101 |
</widget>
|
| 102 |
</item>
|
| 103 |
<item row="1" column="3">
|
| 104 |
-
<widget class="QComboBox" name="
|
| 105 |
<item>
|
| 106 |
<property name="text">
|
| 107 |
<string>,</string>
|
|
@@ -125,7 +125,7 @@
|
|
| 125 |
</widget>
|
| 126 |
</item>
|
| 127 |
<item row="0" column="3">
|
| 128 |
-
<widget class="QPushButton" name="
|
| 129 |
<property name="sizePolicy">
|
| 130 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 131 |
<horstretch>0</horstretch>
|
|
@@ -141,7 +141,7 @@
|
|
| 141 |
</widget>
|
| 142 |
</item>
|
| 143 |
<item row="0" column="1" colspan="2">
|
| 144 |
-
<widget class="QLineEdit" name="
|
| 145 |
<property name="readOnly">
|
| 146 |
<bool>true</bool>
|
| 147 |
</property>
|
|
@@ -153,8 +153,8 @@
|
|
| 153 |
</layout>
|
| 154 |
</widget>
|
| 155 |
</item>
|
| 156 |
-
<item row="
|
| 157 |
-
<widget class="QPushButton" name="
|
| 158 |
<property name="sizePolicy">
|
| 159 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 160 |
<horstretch>0</horstretch>
|
|
@@ -168,12 +168,12 @@
|
|
| 168 |
</size>
|
| 169 |
</property>
|
| 170 |
<property name="text">
|
| 171 |
-
<string>
|
| 172 |
</property>
|
| 173 |
</widget>
|
| 174 |
</item>
|
| 175 |
-
<item row="
|
| 176 |
-
<widget class="QPushButton" name="
|
| 177 |
<property name="sizePolicy">
|
| 178 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 179 |
<horstretch>0</horstretch>
|
|
@@ -187,77 +187,12 @@
|
|
| 187 |
</size>
|
| 188 |
</property>
|
| 189 |
<property name="text">
|
| 190 |
-
<string>
|
| 191 |
</property>
|
| 192 |
</widget>
|
| 193 |
</item>
|
| 194 |
-
<item row="1" column="3" rowspan="2">
|
| 195 |
-
<spacer name="horizontalSpacer_3">
|
| 196 |
-
<property name="orientation">
|
| 197 |
-
<enum>Qt::Horizontal</enum>
|
| 198 |
-
</property>
|
| 199 |
-
<property name="sizeType">
|
| 200 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 201 |
-
</property>
|
| 202 |
-
<property name="sizeHint" stdset="0">
|
| 203 |
-
<size>
|
| 204 |
-
<width>20</width>
|
| 205 |
-
<height>20</height>
|
| 206 |
-
</size>
|
| 207 |
-
</property>
|
| 208 |
-
</spacer>
|
| 209 |
-
</item>
|
| 210 |
-
<item row="1" column="0" rowspan="2">
|
| 211 |
-
<spacer name="horizontalSpacer_2">
|
| 212 |
-
<property name="orientation">
|
| 213 |
-
<enum>Qt::Horizontal</enum>
|
| 214 |
-
</property>
|
| 215 |
-
<property name="sizeType">
|
| 216 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 217 |
-
</property>
|
| 218 |
-
<property name="sizeHint" stdset="0">
|
| 219 |
-
<size>
|
| 220 |
-
<width>20</width>
|
| 221 |
-
<height>20</height>
|
| 222 |
-
</size>
|
| 223 |
-
</property>
|
| 224 |
-
</spacer>
|
| 225 |
-
</item>
|
| 226 |
-
<item row="0" column="1" colspan="2">
|
| 227 |
-
<spacer name="verticalSpacer_2">
|
| 228 |
-
<property name="orientation">
|
| 229 |
-
<enum>Qt::Vertical</enum>
|
| 230 |
-
</property>
|
| 231 |
-
<property name="sizeType">
|
| 232 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 233 |
-
</property>
|
| 234 |
-
<property name="sizeHint" stdset="0">
|
| 235 |
-
<size>
|
| 236 |
-
<width>20</width>
|
| 237 |
-
<height>20</height>
|
| 238 |
-
</size>
|
| 239 |
-
</property>
|
| 240 |
-
</spacer>
|
| 241 |
-
</item>
|
| 242 |
-
<item row="5" column="1" colspan="2">
|
| 243 |
-
<spacer name="verticalSpacer">
|
| 244 |
-
<property name="orientation">
|
| 245 |
-
<enum>Qt::Vertical</enum>
|
| 246 |
-
</property>
|
| 247 |
-
<property name="sizeType">
|
| 248 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 249 |
-
</property>
|
| 250 |
-
<property name="sizeHint" stdset="0">
|
| 251 |
-
<size>
|
| 252 |
-
<width>20</width>
|
| 253 |
-
<height>20</height>
|
| 254 |
-
</size>
|
| 255 |
-
</property>
|
| 256 |
-
</spacer>
|
| 257 |
-
</item>
|
| 258 |
</layout>
|
| 259 |
</widget>
|
| 260 |
-
<widget class="QStatusBar" name="statusbar"/>
|
| 261 |
</widget>
|
| 262 |
<resources/>
|
| 263 |
<connections/>
|
|
|
|
| 6 |
<rect>
|
| 7 |
<x>0</x>
|
| 8 |
<y>0</y>
|
| 9 |
+
<width>960</width>
|
| 10 |
+
<height>916</height>
|
| 11 |
</rect>
|
| 12 |
</property>
|
| 13 |
<property name="font">
|
|
|
|
| 20 |
</property>
|
| 21 |
<widget class="QWidget" name="centralwidget">
|
| 22 |
<layout class="QGridLayout" name="gridLayout_2">
|
| 23 |
+
<item row="1" column="0" colspan="2">
|
| 24 |
+
<widget class="QGroupBox" name="grpGuideOptions">
|
| 25 |
<property name="title">
|
| 26 |
<string>Guide Options</string>
|
| 27 |
</property>
|
| 28 |
<layout class="QGridLayout" name="gridLayout_6">
|
| 29 |
<item row="1" column="0">
|
| 30 |
+
<widget class="QLabel" name="lblLeadingSequence">
|
| 31 |
<property name="text">
|
| 32 |
<string>5' Leading Sequence:</string>
|
| 33 |
</property>
|
| 34 |
</widget>
|
| 35 |
</item>
|
| 36 |
<item row="1" column="1">
|
| 37 |
+
<widget class="QLineEdit" name="ledLeadingsequence">
|
| 38 |
<property name="placeholderText">
|
| 39 |
<string>E.g. promoter, tRNA, homology overhang, etc.</string>
|
| 40 |
</property>
|
| 41 |
</widget>
|
| 42 |
</item>
|
| 43 |
<item row="2" column="1">
|
| 44 |
+
<widget class="QLineEdit" name="ledTrailingSequence">
|
| 45 |
<property name="placeholderText">
|
| 46 |
<string>E.g. gRNA scaffold, terminator, etc.</string>
|
| 47 |
</property>
|
| 48 |
</widget>
|
| 49 |
</item>
|
| 50 |
<item row="2" column="0">
|
| 51 |
+
<widget class="QLabel" name="lblTrailingSequence">
|
| 52 |
<property name="text">
|
| 53 |
<string>3' Trailing Sequence:</string>
|
| 54 |
</property>
|
|
|
|
| 57 |
</layout>
|
| 58 |
</widget>
|
| 59 |
</item>
|
| 60 |
+
<item row="0" column="0" colspan="2">
|
| 61 |
+
<widget class="QGroupBox" name="grpExportOptions">
|
| 62 |
<property name="title">
|
| 63 |
<string>Export Options</string>
|
| 64 |
</property>
|
| 65 |
<layout class="QGridLayout" name="gridLayout_3">
|
| 66 |
<item row="0" column="0">
|
| 67 |
+
<widget class="QLabel" name="lblFilePath">
|
| 68 |
<property name="text">
|
| 69 |
+
<string>File Path:</string>
|
| 70 |
</property>
|
| 71 |
</widget>
|
| 72 |
</item>
|
| 73 |
<item row="1" column="0">
|
| 74 |
+
<widget class="QLabel" name="lblFileName">
|
| 75 |
<property name="sizePolicy">
|
| 76 |
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
| 77 |
<horstretch>0</horstretch>
|
|
|
|
| 84 |
</widget>
|
| 85 |
</item>
|
| 86 |
<item row="1" column="2">
|
| 87 |
+
<widget class="QLabel" name="lblDelimiter">
|
| 88 |
<property name="text">
|
| 89 |
<string>Delimiter:</string>
|
| 90 |
</property>
|
| 91 |
</widget>
|
| 92 |
</item>
|
| 93 |
<item row="1" column="1">
|
| 94 |
+
<widget class="QLineEdit" name="ledFileName">
|
| 95 |
<property name="toolTip">
|
| 96 |
<string><html><head/><body><p>Please write the file name you want.</p><p>Note: it is not necessary to add the file extension.</p></body></html></string>
|
| 97 |
</property>
|
|
|
|
| 101 |
</widget>
|
| 102 |
</item>
|
| 103 |
<item row="1" column="3">
|
| 104 |
+
<widget class="QComboBox" name="cmbDelimiter">
|
| 105 |
<item>
|
| 106 |
<property name="text">
|
| 107 |
<string>,</string>
|
|
|
|
| 125 |
</widget>
|
| 126 |
</item>
|
| 127 |
<item row="0" column="3">
|
| 128 |
+
<widget class="QPushButton" name="pbtnBrowse">
|
| 129 |
<property name="sizePolicy">
|
| 130 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 131 |
<horstretch>0</horstretch>
|
|
|
|
| 141 |
</widget>
|
| 142 |
</item>
|
| 143 |
<item row="0" column="1" colspan="2">
|
| 144 |
+
<widget class="QLineEdit" name="ledFilePath">
|
| 145 |
<property name="readOnly">
|
| 146 |
<bool>true</bool>
|
| 147 |
</property>
|
|
|
|
| 153 |
</layout>
|
| 154 |
</widget>
|
| 155 |
</item>
|
| 156 |
+
<item row="2" column="0" alignment="Qt::AlignLeft">
|
| 157 |
+
<widget class="QPushButton" name="pbtnCancel">
|
| 158 |
<property name="sizePolicy">
|
| 159 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 160 |
<horstretch>0</horstretch>
|
|
|
|
| 168 |
</size>
|
| 169 |
</property>
|
| 170 |
<property name="text">
|
| 171 |
+
<string>Cancel</string>
|
| 172 |
</property>
|
| 173 |
</widget>
|
| 174 |
</item>
|
| 175 |
+
<item row="2" column="1" alignment="Qt::AlignRight">
|
| 176 |
+
<widget class="QPushButton" name="pbtnExport">
|
| 177 |
<property name="sizePolicy">
|
| 178 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 179 |
<horstretch>0</horstretch>
|
|
|
|
| 187 |
</size>
|
| 188 |
</property>
|
| 189 |
<property name="text">
|
| 190 |
+
<string>Export</string>
|
| 191 |
</property>
|
| 192 |
</widget>
|
| 193 |
</item>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
</layout>
|
| 195 |
</widget>
|
|
|
|
| 196 |
</widget>
|
| 197 |
<resources/>
|
| 198 |
<connections/>
|
|
@@ -80,7 +80,32 @@
|
|
| 80 |
</property>
|
| 81 |
</widget>
|
| 82 |
</item>
|
| 83 |
-
<item
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
<widget class="QPushButton" name="pbtnViewTargets">
|
| 85 |
<property name="sizePolicy">
|
| 86 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
|
|
|
| 80 |
</property>
|
| 81 |
</widget>
|
| 82 |
</item>
|
| 83 |
+
<item>
|
| 84 |
+
<widget class="QPushButton" name="pbtnGenerateLibrary">
|
| 85 |
+
<property name="sizePolicy">
|
| 86 |
+
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 87 |
+
<horstretch>0</horstretch>
|
| 88 |
+
<verstretch>0</verstretch>
|
| 89 |
+
</sizepolicy>
|
| 90 |
+
</property>
|
| 91 |
+
<property name="minimumSize">
|
| 92 |
+
<size>
|
| 93 |
+
<width>0</width>
|
| 94 |
+
<height>0</height>
|
| 95 |
+
</size>
|
| 96 |
+
</property>
|
| 97 |
+
<property name="maximumSize">
|
| 98 |
+
<size>
|
| 99 |
+
<width>16777215</width>
|
| 100 |
+
<height>16777215</height>
|
| 101 |
+
</size>
|
| 102 |
+
</property>
|
| 103 |
+
<property name="text">
|
| 104 |
+
<string>Generate Library</string>
|
| 105 |
+
</property>
|
| 106 |
+
</widget>
|
| 107 |
+
</item>
|
| 108 |
+
<item>
|
| 109 |
<widget class="QPushButton" name="pbtnViewTargets">
|
| 110 |
<property name="sizePolicy">
|
| 111 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
|
@@ -16,12 +16,12 @@
|
|
| 16 |
<widget class="QWidget" name="centralwidget">
|
| 17 |
<layout class="QGridLayout" name="gridLayout_2">
|
| 18 |
<item row="1" column="1">
|
| 19 |
-
<layout class="QGridLayout" name="gridLayout" rowstretch="1,0,0,0
|
| 20 |
<property name="sizeConstraint">
|
| 21 |
<enum>QLayout::SetDefaultConstraint</enum>
|
| 22 |
</property>
|
| 23 |
-
<item row="
|
| 24 |
-
<widget class="QPushButton" name="
|
| 25 |
<property name="sizePolicy">
|
| 26 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 27 |
<horstretch>0</horstretch>
|
|
@@ -39,8 +39,8 @@
|
|
| 39 |
</property>
|
| 40 |
</widget>
|
| 41 |
</item>
|
| 42 |
-
<item row="
|
| 43 |
-
<widget class="QGroupBox" name="
|
| 44 |
<property name="sizePolicy">
|
| 45 |
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
| 46 |
<horstretch>0</horstretch>
|
|
@@ -52,7 +52,7 @@
|
|
| 52 |
</property>
|
| 53 |
<layout class="QGridLayout" name="gridLayout_7" rowstretch="0,0,0,0">
|
| 54 |
<item row="2" column="2">
|
| 55 |
-
<widget class="QPushButton" name="
|
| 56 |
<property name="sizePolicy">
|
| 57 |
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
| 58 |
<horstretch>0</horstretch>
|
|
@@ -84,7 +84,7 @@
|
|
| 84 |
</spacer>
|
| 85 |
</item>
|
| 86 |
<item row="2" column="0">
|
| 87 |
-
<widget class="QLabel" name="
|
| 88 |
<property name="sizePolicy">
|
| 89 |
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
| 90 |
<horstretch>0</horstretch>
|
|
@@ -95,12 +95,12 @@
|
|
| 95 |
<string><html><head/><body><p><span style=" font-size:12pt;">This is the directory that CASPER will write the library CSV to.</span></p></body></html></string>
|
| 96 |
</property>
|
| 97 |
<property name="text">
|
| 98 |
-
<string>Output path:</string>
|
| 99 |
</property>
|
| 100 |
</widget>
|
| 101 |
</item>
|
| 102 |
<item row="1" column="0">
|
| 103 |
-
<widget class="QLabel" name="
|
| 104 |
<property name="sizePolicy">
|
| 105 |
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
| 106 |
<horstretch>0</horstretch>
|
|
@@ -116,7 +116,7 @@
|
|
| 116 |
</widget>
|
| 117 |
</item>
|
| 118 |
<item row="1" column="1" colspan="2">
|
| 119 |
-
<widget class="QLineEdit" name="
|
| 120 |
<property name="sizePolicy">
|
| 121 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 122 |
<horstretch>0</horstretch>
|
|
@@ -126,7 +126,7 @@
|
|
| 126 |
</widget>
|
| 127 |
</item>
|
| 128 |
<item row="2" column="1">
|
| 129 |
-
<widget class="QLineEdit" name="
|
| 130 |
<property name="sizePolicy">
|
| 131 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 132 |
<horstretch>0</horstretch>
|
|
@@ -154,8 +154,8 @@
|
|
| 154 |
</layout>
|
| 155 |
</widget>
|
| 156 |
</item>
|
| 157 |
-
<item row="
|
| 158 |
-
<widget class="QPushButton" name="
|
| 159 |
<property name="sizePolicy">
|
| 160 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 161 |
<horstretch>0</horstretch>
|
|
@@ -176,21 +176,8 @@
|
|
| 176 |
</property>
|
| 177 |
</widget>
|
| 178 |
</item>
|
| 179 |
-
<item row="1" column="
|
| 180 |
-
<widget class="
|
| 181 |
-
<property name="sizePolicy">
|
| 182 |
-
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 183 |
-
<horstretch>0</horstretch>
|
| 184 |
-
<verstretch>0</verstretch>
|
| 185 |
-
</sizepolicy>
|
| 186 |
-
</property>
|
| 187 |
-
<property name="orientation">
|
| 188 |
-
<enum>Qt::Horizontal</enum>
|
| 189 |
-
</property>
|
| 190 |
-
</widget>
|
| 191 |
-
</item>
|
| 192 |
-
<item row="2" column="1">
|
| 193 |
-
<widget class="QGroupBox" name="Step3">
|
| 194 |
<property name="sizePolicy">
|
| 195 |
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
| 196 |
<horstretch>0</horstretch>
|
|
@@ -205,7 +192,7 @@
|
|
| 205 |
<enum>QLayout::SetDefaultConstraint</enum>
|
| 206 |
</property>
|
| 207 |
<item row="3" column="1">
|
| 208 |
-
<widget class="QCheckBox" name="
|
| 209 |
<property name="sizePolicy">
|
| 210 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 211 |
<horstretch>0</horstretch>
|
|
@@ -218,7 +205,7 @@
|
|
| 218 |
</widget>
|
| 219 |
</item>
|
| 220 |
<item row="1" column="0">
|
| 221 |
-
<widget class="QLabel" name="
|
| 222 |
<property name="sizePolicy">
|
| 223 |
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 224 |
<horstretch>0</horstretch>
|
|
@@ -234,7 +221,7 @@
|
|
| 234 |
</widget>
|
| 235 |
</item>
|
| 236 |
<item row="2" column="1">
|
| 237 |
-
<widget class="QLineEdit" name="
|
| 238 |
<property name="sizePolicy">
|
| 239 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 240 |
<horstretch>0</horstretch>
|
|
@@ -247,7 +234,7 @@
|
|
| 247 |
</widget>
|
| 248 |
</item>
|
| 249 |
<item row="1" column="1">
|
| 250 |
-
<widget class="QLineEdit" name="
|
| 251 |
<property name="sizePolicy">
|
| 252 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 253 |
<horstretch>0</horstretch>
|
|
@@ -276,7 +263,7 @@
|
|
| 276 |
</spacer>
|
| 277 |
</item>
|
| 278 |
<item row="2" column="0">
|
| 279 |
-
<widget class="QLabel" name="
|
| 280 |
<property name="sizePolicy">
|
| 281 |
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 282 |
<horstretch>0</horstretch>
|
|
@@ -292,7 +279,7 @@
|
|
| 292 |
</widget>
|
| 293 |
</item>
|
| 294 |
<item row="3" column="0">
|
| 295 |
-
<widget class="QLabel" name="
|
| 296 |
<property name="sizePolicy">
|
| 297 |
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 298 |
<horstretch>0</horstretch>
|
|
@@ -327,7 +314,7 @@
|
|
| 327 |
</widget>
|
| 328 |
</item>
|
| 329 |
<item row="0" column="0" colspan="2">
|
| 330 |
-
<widget class="QLabel" name="
|
| 331 |
<property name="sizePolicy">
|
| 332 |
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 333 |
<horstretch>0</horstretch>
|
|
@@ -339,8 +326,8 @@
|
|
| 339 |
</property>
|
| 340 |
</widget>
|
| 341 |
</item>
|
| 342 |
-
<item row="
|
| 343 |
-
<widget class="QGroupBox" name="
|
| 344 |
<property name="sizePolicy">
|
| 345 |
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
| 346 |
<horstretch>0</horstretch>
|
|
@@ -352,7 +339,7 @@
|
|
| 352 |
</property>
|
| 353 |
<layout class="QGridLayout" name="gridLayout_9" rowstretch="0,0,0,0,0,0" columnstretch="0,0">
|
| 354 |
<item row="1" column="0">
|
| 355 |
-
<widget class="QLabel" name="
|
| 356 |
<property name="sizePolicy">
|
| 357 |
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
| 358 |
<horstretch>0</horstretch>
|
|
@@ -384,7 +371,7 @@
|
|
| 384 |
</spacer>
|
| 385 |
</item>
|
| 386 |
<item row="4" column="1">
|
| 387 |
-
<widget class="QProgressBar" name="
|
| 388 |
<property name="sizePolicy">
|
| 389 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 390 |
<horstretch>0</horstretch>
|
|
@@ -397,7 +384,7 @@
|
|
| 397 |
</widget>
|
| 398 |
</item>
|
| 399 |
<item row="1" column="1">
|
| 400 |
-
<widget class="QCheckBox" name="
|
| 401 |
<property name="sizePolicy">
|
| 402 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 403 |
<horstretch>0</horstretch>
|
|
@@ -410,7 +397,7 @@
|
|
| 410 |
</widget>
|
| 411 |
</item>
|
| 412 |
<item row="2" column="1">
|
| 413 |
-
<widget class="QComboBox" name="
|
| 414 |
<property name="sizePolicy">
|
| 415 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 416 |
<horstretch>0</horstretch>
|
|
@@ -420,7 +407,7 @@
|
|
| 420 |
</widget>
|
| 421 |
</item>
|
| 422 |
<item row="2" column="0">
|
| 423 |
-
<widget class="QLabel" name="
|
| 424 |
<property name="sizePolicy">
|
| 425 |
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
| 426 |
<horstretch>0</horstretch>
|
|
@@ -436,7 +423,7 @@
|
|
| 436 |
</widget>
|
| 437 |
</item>
|
| 438 |
<item row="3" column="1">
|
| 439 |
-
<widget class="QLineEdit" name="
|
| 440 |
<property name="sizePolicy">
|
| 441 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 442 |
<horstretch>0</horstretch>
|
|
@@ -449,7 +436,7 @@
|
|
| 449 |
</widget>
|
| 450 |
</item>
|
| 451 |
<item row="4" column="0">
|
| 452 |
-
<widget class="QLabel" name="
|
| 453 |
<property name="sizePolicy">
|
| 454 |
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
| 455 |
<horstretch>0</horstretch>
|
|
@@ -462,7 +449,7 @@
|
|
| 462 |
</widget>
|
| 463 |
</item>
|
| 464 |
<item row="3" column="0">
|
| 465 |
-
<widget class="QLabel" name="
|
| 466 |
<property name="sizePolicy">
|
| 467 |
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
| 468 |
<horstretch>0</horstretch>
|
|
@@ -496,8 +483,8 @@
|
|
| 496 |
</layout>
|
| 497 |
</widget>
|
| 498 |
</item>
|
| 499 |
-
<item row="
|
| 500 |
-
<widget class="QGroupBox" name="
|
| 501 |
<property name="sizePolicy">
|
| 502 |
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
| 503 |
<horstretch>0</horstretch>
|
|
@@ -509,7 +496,7 @@
|
|
| 509 |
</property>
|
| 510 |
<layout class="QGridLayout" name="gridLayout_10">
|
| 511 |
<item row="1" column="1" colspan="3">
|
| 512 |
-
<widget class="QComboBox" name="
|
| 513 |
<property name="sizePolicy">
|
| 514 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 515 |
<horstretch>0</horstretch>
|
|
@@ -519,7 +506,7 @@
|
|
| 519 |
</widget>
|
| 520 |
</item>
|
| 521 |
<item row="2" column="0">
|
| 522 |
-
<widget class="QLabel" name="
|
| 523 |
<property name="sizePolicy">
|
| 524 |
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
| 525 |
<horstretch>0</horstretch>
|
|
@@ -535,7 +522,7 @@
|
|
| 535 |
</widget>
|
| 536 |
</item>
|
| 537 |
<item row="1" column="0">
|
| 538 |
-
<widget class="QLabel" name="
|
| 539 |
<property name="sizePolicy">
|
| 540 |
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
| 541 |
<horstretch>0</horstretch>
|
|
@@ -551,7 +538,7 @@
|
|
| 551 |
</widget>
|
| 552 |
</item>
|
| 553 |
<item row="3" column="0" colspan="4">
|
| 554 |
-
<widget class="QLabel" name="
|
| 555 |
<property name="sizePolicy">
|
| 556 |
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
| 557 |
<horstretch>0</horstretch>
|
|
@@ -564,7 +551,7 @@
|
|
| 564 |
</widget>
|
| 565 |
</item>
|
| 566 |
<item row="2" column="1">
|
| 567 |
-
<widget class="QLineEdit" name="
|
| 568 |
<property name="sizePolicy">
|
| 569 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 570 |
<horstretch>0</horstretch>
|
|
@@ -577,7 +564,7 @@
|
|
| 577 |
</widget>
|
| 578 |
</item>
|
| 579 |
<item row="2" column="2">
|
| 580 |
-
<widget class="QLabel" name="
|
| 581 |
<property name="sizePolicy">
|
| 582 |
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
| 583 |
<horstretch>0</horstretch>
|
|
@@ -593,7 +580,7 @@
|
|
| 593 |
</widget>
|
| 594 |
</item>
|
| 595 |
<item row="2" column="3">
|
| 596 |
-
<widget class="QLineEdit" name="
|
| 597 |
<property name="sizePolicy">
|
| 598 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 599 |
<horstretch>0</horstretch>
|
|
@@ -708,23 +695,22 @@
|
|
| 708 |
</item>
|
| 709 |
</layout>
|
| 710 |
</widget>
|
| 711 |
-
<widget class="QStatusBar" name="statusbar"/>
|
| 712 |
</widget>
|
| 713 |
<tabstops>
|
| 714 |
-
<tabstop>
|
| 715 |
-
<tabstop>
|
| 716 |
-
<tabstop>
|
| 717 |
-
<tabstop>
|
| 718 |
-
<tabstop>
|
| 719 |
-
<tabstop>
|
| 720 |
-
<tabstop>
|
| 721 |
-
<tabstop>
|
| 722 |
-
<tabstop>
|
| 723 |
-
<tabstop>
|
| 724 |
-
<tabstop>
|
| 725 |
-
<tabstop>
|
| 726 |
-
<tabstop>
|
| 727 |
-
<tabstop>
|
| 728 |
</tabstops>
|
| 729 |
<resources/>
|
| 730 |
<connections/>
|
|
|
|
| 16 |
<widget class="QWidget" name="centralwidget">
|
| 17 |
<layout class="QGridLayout" name="gridLayout_2">
|
| 18 |
<item row="1" column="1">
|
| 19 |
+
<layout class="QGridLayout" name="gridLayout" rowstretch="1,0,0,0" columnstretch="1,0">
|
| 20 |
<property name="sizeConstraint">
|
| 21 |
<enum>QLayout::SetDefaultConstraint</enum>
|
| 22 |
</property>
|
| 23 |
+
<item row="3" column="1" alignment="Qt::AlignRight">
|
| 24 |
+
<widget class="QPushButton" name="pbtnSubmit">
|
| 25 |
<property name="sizePolicy">
|
| 26 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 27 |
<horstretch>0</horstretch>
|
|
|
|
| 39 |
</property>
|
| 40 |
</widget>
|
| 41 |
</item>
|
| 42 |
+
<item row="2" column="1">
|
| 43 |
+
<widget class="QGroupBox" name="grpOutputLocation">
|
| 44 |
<property name="sizePolicy">
|
| 45 |
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
| 46 |
<horstretch>0</horstretch>
|
|
|
|
| 52 |
</property>
|
| 53 |
<layout class="QGridLayout" name="gridLayout_7" rowstretch="0,0,0,0">
|
| 54 |
<item row="2" column="2">
|
| 55 |
+
<widget class="QPushButton" name="pbtnBrowse">
|
| 56 |
<property name="sizePolicy">
|
| 57 |
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
| 58 |
<horstretch>0</horstretch>
|
|
|
|
| 84 |
</spacer>
|
| 85 |
</item>
|
| 86 |
<item row="2" column="0">
|
| 87 |
+
<widget class="QLabel" name="lblFilePath">
|
| 88 |
<property name="sizePolicy">
|
| 89 |
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
| 90 |
<horstretch>0</horstretch>
|
|
|
|
| 95 |
<string><html><head/><body><p><span style=" font-size:12pt;">This is the directory that CASPER will write the library CSV to.</span></p></body></html></string>
|
| 96 |
</property>
|
| 97 |
<property name="text">
|
| 98 |
+
<string>Output file path:</string>
|
| 99 |
</property>
|
| 100 |
</widget>
|
| 101 |
</item>
|
| 102 |
<item row="1" column="0">
|
| 103 |
+
<widget class="QLabel" name="lblFileName">
|
| 104 |
<property name="sizePolicy">
|
| 105 |
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
| 106 |
<horstretch>0</horstretch>
|
|
|
|
| 116 |
</widget>
|
| 117 |
</item>
|
| 118 |
<item row="1" column="1" colspan="2">
|
| 119 |
+
<widget class="QLineEdit" name="ledFileName">
|
| 120 |
<property name="sizePolicy">
|
| 121 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 122 |
<horstretch>0</horstretch>
|
|
|
|
| 126 |
</widget>
|
| 127 |
</item>
|
| 128 |
<item row="2" column="1">
|
| 129 |
+
<widget class="QLineEdit" name="ledFilePath">
|
| 130 |
<property name="sizePolicy">
|
| 131 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 132 |
<horstretch>0</horstretch>
|
|
|
|
| 154 |
</layout>
|
| 155 |
</widget>
|
| 156 |
</item>
|
| 157 |
+
<item row="3" column="0" alignment="Qt::AlignLeft">
|
| 158 |
+
<widget class="QPushButton" name="pbtnCancel">
|
| 159 |
<property name="sizePolicy">
|
| 160 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 161 |
<horstretch>0</horstretch>
|
|
|
|
| 176 |
</property>
|
| 177 |
</widget>
|
| 178 |
</item>
|
| 179 |
+
<item row="1" column="1">
|
| 180 |
+
<widget class="QGroupBox" name="grpTargetingGuidelines">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 181 |
<property name="sizePolicy">
|
| 182 |
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
| 183 |
<horstretch>0</horstretch>
|
|
|
|
| 192 |
<enum>QLayout::SetDefaultConstraint</enum>
|
| 193 |
</property>
|
| 194 |
<item row="3" column="1">
|
| 195 |
+
<widget class="QCheckBox" name="chkModifyParameters">
|
| 196 |
<property name="sizePolicy">
|
| 197 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 198 |
<horstretch>0</horstretch>
|
|
|
|
| 205 |
</widget>
|
| 206 |
</item>
|
| 207 |
<item row="1" column="0">
|
| 208 |
+
<widget class="QLabel" name="lblSpaceBetweenGuides">
|
| 209 |
<property name="sizePolicy">
|
| 210 |
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 211 |
<horstretch>0</horstretch>
|
|
|
|
| 221 |
</widget>
|
| 222 |
</item>
|
| 223 |
<item row="2" column="1">
|
| 224 |
+
<widget class="QLineEdit" name="led5PrimeSpecificity">
|
| 225 |
<property name="sizePolicy">
|
| 226 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 227 |
<horstretch>0</horstretch>
|
|
|
|
| 234 |
</widget>
|
| 235 |
</item>
|
| 236 |
<item row="1" column="1">
|
| 237 |
+
<widget class="QLineEdit" name="ledSpaceBetweenGuides">
|
| 238 |
<property name="sizePolicy">
|
| 239 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 240 |
<horstretch>0</horstretch>
|
|
|
|
| 263 |
</spacer>
|
| 264 |
</item>
|
| 265 |
<item row="2" column="0">
|
| 266 |
+
<widget class="QLabel" name="lbl5PrimeSpecificity">
|
| 267 |
<property name="sizePolicy">
|
| 268 |
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 269 |
<horstretch>0</horstretch>
|
|
|
|
| 279 |
</widget>
|
| 280 |
</item>
|
| 281 |
<item row="3" column="0">
|
| 282 |
+
<widget class="QLabel" name="lblModifyParameters">
|
| 283 |
<property name="sizePolicy">
|
| 284 |
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 285 |
<horstretch>0</horstretch>
|
|
|
|
| 314 |
</widget>
|
| 315 |
</item>
|
| 316 |
<item row="0" column="0" colspan="2">
|
| 317 |
+
<widget class="QLabel" name="lblTitle">
|
| 318 |
<property name="sizePolicy">
|
| 319 |
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 320 |
<horstretch>0</horstretch>
|
|
|
|
| 326 |
</property>
|
| 327 |
</widget>
|
| 328 |
</item>
|
| 329 |
+
<item row="2" column="0">
|
| 330 |
+
<widget class="QGroupBox" name="grpQualityFiltering">
|
| 331 |
<property name="sizePolicy">
|
| 332 |
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
| 333 |
<horstretch>0</horstretch>
|
|
|
|
| 339 |
</property>
|
| 340 |
<layout class="QGridLayout" name="gridLayout_9" rowstretch="0,0,0,0,0,0" columnstretch="0,0">
|
| 341 |
<item row="1" column="0">
|
| 342 |
+
<widget class="QLabel" name="lblFindOffTargets">
|
| 343 |
<property name="sizePolicy">
|
| 344 |
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
| 345 |
<horstretch>0</horstretch>
|
|
|
|
| 371 |
</spacer>
|
| 372 |
</item>
|
| 373 |
<item row="4" column="1">
|
| 374 |
+
<widget class="QProgressBar" name="progBar">
|
| 375 |
<property name="sizePolicy">
|
| 376 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 377 |
<horstretch>0</horstretch>
|
|
|
|
| 384 |
</widget>
|
| 385 |
</item>
|
| 386 |
<item row="1" column="1">
|
| 387 |
+
<widget class="QCheckBox" name="chkFindOffTargets">
|
| 388 |
<property name="sizePolicy">
|
| 389 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 390 |
<horstretch>0</horstretch>
|
|
|
|
| 397 |
</widget>
|
| 398 |
</item>
|
| 399 |
<item row="2" column="1">
|
| 400 |
+
<widget class="QComboBox" name="cmbMinimumOnTargetScore">
|
| 401 |
<property name="sizePolicy">
|
| 402 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 403 |
<horstretch>0</horstretch>
|
|
|
|
| 407 |
</widget>
|
| 408 |
</item>
|
| 409 |
<item row="2" column="0">
|
| 410 |
+
<widget class="QLabel" name="lblMinimumOnTargetScore">
|
| 411 |
<property name="sizePolicy">
|
| 412 |
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
| 413 |
<horstretch>0</horstretch>
|
|
|
|
| 423 |
</widget>
|
| 424 |
</item>
|
| 425 |
<item row="3" column="1">
|
| 426 |
+
<widget class="QLineEdit" name="cmbMaximumOffTargetScore">
|
| 427 |
<property name="sizePolicy">
|
| 428 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 429 |
<horstretch>0</horstretch>
|
|
|
|
| 436 |
</widget>
|
| 437 |
</item>
|
| 438 |
<item row="4" column="0">
|
| 439 |
+
<widget class="QLabel" name="lblOffTargetProgress">
|
| 440 |
<property name="sizePolicy">
|
| 441 |
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
| 442 |
<horstretch>0</horstretch>
|
|
|
|
| 449 |
</widget>
|
| 450 |
</item>
|
| 451 |
<item row="3" column="0">
|
| 452 |
+
<widget class="QLabel" name="lblMaximumOffTargetScore">
|
| 453 |
<property name="sizePolicy">
|
| 454 |
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
| 455 |
<horstretch>0</horstretch>
|
|
|
|
| 483 |
</layout>
|
| 484 |
</widget>
|
| 485 |
</item>
|
| 486 |
+
<item row="1" column="0">
|
| 487 |
+
<widget class="QGroupBox" name="grpLibrarySizeAndLocation">
|
| 488 |
<property name="sizePolicy">
|
| 489 |
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
| 490 |
<horstretch>0</horstretch>
|
|
|
|
| 496 |
</property>
|
| 497 |
<layout class="QGridLayout" name="gridLayout_10">
|
| 498 |
<item row="1" column="1" colspan="3">
|
| 499 |
+
<widget class="QComboBox" name="cmbGuidesPerGene">
|
| 500 |
<property name="sizePolicy">
|
| 501 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 502 |
<horstretch>0</horstretch>
|
|
|
|
| 506 |
</widget>
|
| 507 |
</item>
|
| 508 |
<item row="2" column="0">
|
| 509 |
+
<widget class="QLabel" name="lblTargetingRange">
|
| 510 |
<property name="sizePolicy">
|
| 511 |
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
| 512 |
<horstretch>0</horstretch>
|
|
|
|
| 522 |
</widget>
|
| 523 |
</item>
|
| 524 |
<item row="1" column="0">
|
| 525 |
+
<widget class="QLabel" name="lblGuidesPerGene">
|
| 526 |
<property name="sizePolicy">
|
| 527 |
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
| 528 |
<horstretch>0</horstretch>
|
|
|
|
| 538 |
</widget>
|
| 539 |
</item>
|
| 540 |
<item row="3" column="0" colspan="4">
|
| 541 |
+
<widget class="QLabel" name="lblStartEndDescription">
|
| 542 |
<property name="sizePolicy">
|
| 543 |
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
| 544 |
<horstretch>0</horstretch>
|
|
|
|
| 551 |
</widget>
|
| 552 |
</item>
|
| 553 |
<item row="2" column="1">
|
| 554 |
+
<widget class="QLineEdit" name="ledTargetRangeStart">
|
| 555 |
<property name="sizePolicy">
|
| 556 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 557 |
<horstretch>0</horstretch>
|
|
|
|
| 564 |
</widget>
|
| 565 |
</item>
|
| 566 |
<item row="2" column="2">
|
| 567 |
+
<widget class="QLabel" name="lblTo">
|
| 568 |
<property name="sizePolicy">
|
| 569 |
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
| 570 |
<horstretch>0</horstretch>
|
|
|
|
| 580 |
</widget>
|
| 581 |
</item>
|
| 582 |
<item row="2" column="3">
|
| 583 |
+
<widget class="QLineEdit" name="ledTargetRangeEnd">
|
| 584 |
<property name="sizePolicy">
|
| 585 |
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 586 |
<horstretch>0</horstretch>
|
|
|
|
| 695 |
</item>
|
| 696 |
</layout>
|
| 697 |
</widget>
|
|
|
|
| 698 |
</widget>
|
| 699 |
<tabstops>
|
| 700 |
+
<tabstop>cmbGuidesPerGene</tabstop>
|
| 701 |
+
<tabstop>ledTargetRangeStart</tabstop>
|
| 702 |
+
<tabstop>ledTargetRangeEnd</tabstop>
|
| 703 |
+
<tabstop>chkFindOffTargets</tabstop>
|
| 704 |
+
<tabstop>cmbMinimumOnTargetScore</tabstop>
|
| 705 |
+
<tabstop>cmbMaximumOffTargetScore</tabstop>
|
| 706 |
+
<tabstop>ledSpaceBetweenGuides</tabstop>
|
| 707 |
+
<tabstop>led5PrimeSpecificity</tabstop>
|
| 708 |
+
<tabstop>chkModifyParameters</tabstop>
|
| 709 |
+
<tabstop>ledFileName</tabstop>
|
| 710 |
+
<tabstop>ledFilePath</tabstop>
|
| 711 |
+
<tabstop>pbtnBrowse</tabstop>
|
| 712 |
+
<tabstop>pbtnCancel</tabstop>
|
| 713 |
+
<tabstop>pbtnSubmit</tabstop>
|
| 714 |
</tabstops>
|
| 715 |
<resources/>
|
| 716 |
<connections/>
|
|
@@ -1,7 +1,7 @@
|
|
| 1 |
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
<ui version="4.0">
|
| 3 |
<class>MainWindow</class>
|
| 4 |
-
<widget class="
|
| 5 |
<property name="enabled">
|
| 6 |
<bool>true</bool>
|
| 7 |
</property>
|
|
@@ -9,8 +9,8 @@
|
|
| 9 |
<rect>
|
| 10 |
<x>0</x>
|
| 11 |
<y>0</y>
|
| 12 |
-
<width>
|
| 13 |
-
<height>
|
| 14 |
</rect>
|
| 15 |
</property>
|
| 16 |
<property name="font">
|
|
@@ -29,6 +29,14 @@
|
|
| 29 |
<string notr="true"/>
|
| 30 |
</property>
|
| 31 |
<widget class="QWidget" name="centralwidget">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
<property name="font">
|
| 33 |
<font>
|
| 34 |
<family>Arial</family>
|
|
@@ -41,7 +49,7 @@
|
|
| 41 |
<property name="styleSheet">
|
| 42 |
<string notr="true"/>
|
| 43 |
</property>
|
| 44 |
-
<layout class="QGridLayout" name="
|
| 45 |
<item row="4" column="0" rowspan="2" colspan="2">
|
| 46 |
<layout class="QGridLayout" name="gridStep1Step2Step3">
|
| 47 |
<property name="sizeConstraint">
|
|
@@ -103,15 +111,6 @@
|
|
| 103 |
<height>16777215</height>
|
| 104 |
</size>
|
| 105 |
</property>
|
| 106 |
-
<property name="font">
|
| 107 |
-
<font>
|
| 108 |
-
<family>Arial</family>
|
| 109 |
-
<pointsize>8</pointsize>
|
| 110 |
-
<weight>50</weight>
|
| 111 |
-
<italic>false</italic>
|
| 112 |
-
<bold>false</bold>
|
| 113 |
-
</font>
|
| 114 |
-
</property>
|
| 115 |
</widget>
|
| 116 |
</item>
|
| 117 |
<item row="1" column="1">
|
|
@@ -137,15 +136,6 @@
|
|
| 137 |
<height>16777215</height>
|
| 138 |
</size>
|
| 139 |
</property>
|
| 140 |
-
<property name="font">
|
| 141 |
-
<font>
|
| 142 |
-
<family>Arial</family>
|
| 143 |
-
<pointsize>8</pointsize>
|
| 144 |
-
<weight>50</weight>
|
| 145 |
-
<italic>false</italic>
|
| 146 |
-
<bold>false</bold>
|
| 147 |
-
</font>
|
| 148 |
-
</property>
|
| 149 |
</widget>
|
| 150 |
</item>
|
| 151 |
<item row="1" column="0">
|
|
@@ -442,33 +432,14 @@
|
|
| 442 |
<property name="topMargin">
|
| 443 |
<number>15</number>
|
| 444 |
</property>
|
| 445 |
-
<item row="0" column="
|
| 446 |
-
<widget class="QRadioButton" name="
|
| 447 |
-
<property name="text">
|
| 448 |
-
<string>Sequence</string>
|
| 449 |
-
</property>
|
| 450 |
-
</widget>
|
| 451 |
-
</item>
|
| 452 |
-
<item row="1" column="0" colspan="3">
|
| 453 |
-
<widget class="QPlainTextEdit" name="txtedGeneEntry">
|
| 454 |
<property name="sizePolicy">
|
| 455 |
-
<sizepolicy hsizetype="Preferred" vsizetype="
|
| 456 |
<horstretch>0</horstretch>
|
| 457 |
<verstretch>0</verstretch>
|
| 458 |
</sizepolicy>
|
| 459 |
</property>
|
| 460 |
-
<property name="minimumSize">
|
| 461 |
-
<size>
|
| 462 |
-
<width>250</width>
|
| 463 |
-
<height>0</height>
|
| 464 |
-
</size>
|
| 465 |
-
</property>
|
| 466 |
-
<property name="maximumSize">
|
| 467 |
-
<size>
|
| 468 |
-
<width>16777215</width>
|
| 469 |
-
<height>16777215</height>
|
| 470 |
-
</size>
|
| 471 |
-
</property>
|
| 472 |
<property name="font">
|
| 473 |
<font>
|
| 474 |
<family>Arial</family>
|
|
@@ -478,41 +449,32 @@
|
|
| 478 |
<bold>false</bold>
|
| 479 |
</font>
|
| 480 |
</property>
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
<item row="4" column="0" colspan="3">
|
| 484 |
-
<widget class="QProgressBar" name="progBarFindTargets">
|
| 485 |
-
<property name="sizePolicy">
|
| 486 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 487 |
-
<horstretch>0</horstretch>
|
| 488 |
-
<verstretch>0</verstretch>
|
| 489 |
-
</sizepolicy>
|
| 490 |
-
</property>
|
| 491 |
-
<property name="minimumSize">
|
| 492 |
-
<size>
|
| 493 |
-
<width>0</width>
|
| 494 |
-
<height>0</height>
|
| 495 |
-
</size>
|
| 496 |
</property>
|
| 497 |
-
<property name="
|
| 498 |
-
<
|
| 499 |
</property>
|
| 500 |
</widget>
|
| 501 |
</item>
|
| 502 |
-
<item row="
|
| 503 |
-
<widget class="
|
| 504 |
-
<property name="
|
| 505 |
-
<
|
| 506 |
</property>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 507 |
<property name="sizePolicy">
|
| 508 |
-
<sizepolicy hsizetype="Preferred" vsizetype="
|
| 509 |
<horstretch>0</horstretch>
|
| 510 |
<verstretch>0</verstretch>
|
| 511 |
</sizepolicy>
|
| 512 |
</property>
|
| 513 |
<property name="minimumSize">
|
| 514 |
<size>
|
| 515 |
-
<width>
|
| 516 |
<height>0</height>
|
| 517 |
</size>
|
| 518 |
</property>
|
|
@@ -522,19 +484,6 @@
|
|
| 522 |
<height>16777215</height>
|
| 523 |
</size>
|
| 524 |
</property>
|
| 525 |
-
<property name="text">
|
| 526 |
-
<string>Find Targets</string>
|
| 527 |
-
</property>
|
| 528 |
-
</widget>
|
| 529 |
-
</item>
|
| 530 |
-
<item row="0" column="0">
|
| 531 |
-
<widget class="QRadioButton" name="rbtnFeature">
|
| 532 |
-
<property name="sizePolicy">
|
| 533 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
| 534 |
-
<horstretch>0</horstretch>
|
| 535 |
-
<verstretch>0</verstretch>
|
| 536 |
-
</sizepolicy>
|
| 537 |
-
</property>
|
| 538 |
<property name="font">
|
| 539 |
<font>
|
| 540 |
<family>Arial</family>
|
|
@@ -544,12 +493,6 @@
|
|
| 544 |
<bold>false</bold>
|
| 545 |
</font>
|
| 546 |
</property>
|
| 547 |
-
<property name="text">
|
| 548 |
-
<string>Feature</string>
|
| 549 |
-
</property>
|
| 550 |
-
<property name="checked">
|
| 551 |
-
<bool>true</bool>
|
| 552 |
-
</property>
|
| 553 |
</widget>
|
| 554 |
</item>
|
| 555 |
<item row="0" column="1">
|
|
@@ -580,10 +523,10 @@
|
|
| 580 |
</property>
|
| 581 |
</widget>
|
| 582 |
</item>
|
| 583 |
-
<item row="
|
| 584 |
<layout class="QHBoxLayout" name="horizontalLayout">
|
| 585 |
<item>
|
| 586 |
-
<widget class="QPushButton" name="
|
| 587 |
<property name="sizePolicy">
|
| 588 |
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 589 |
<horstretch>0</horstretch>
|
|
@@ -597,32 +540,7 @@
|
|
| 597 |
</size>
|
| 598 |
</property>
|
| 599 |
<property name="text">
|
| 600 |
-
<string>
|
| 601 |
-
</property>
|
| 602 |
-
</widget>
|
| 603 |
-
</item>
|
| 604 |
-
<item>
|
| 605 |
-
<widget class="QPushButton" name="pbtnGenerateLibrary">
|
| 606 |
-
<property name="sizePolicy">
|
| 607 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 608 |
-
<horstretch>0</horstretch>
|
| 609 |
-
<verstretch>0</verstretch>
|
| 610 |
-
</sizepolicy>
|
| 611 |
-
</property>
|
| 612 |
-
<property name="minimumSize">
|
| 613 |
-
<size>
|
| 614 |
-
<width>0</width>
|
| 615 |
-
<height>0</height>
|
| 616 |
-
</size>
|
| 617 |
-
</property>
|
| 618 |
-
<property name="maximumSize">
|
| 619 |
-
<size>
|
| 620 |
-
<width>16777215</width>
|
| 621 |
-
<height>16777215</height>
|
| 622 |
-
</size>
|
| 623 |
-
</property>
|
| 624 |
-
<property name="text">
|
| 625 |
-
<string>Generate Library</string>
|
| 626 |
</property>
|
| 627 |
</widget>
|
| 628 |
</item>
|
|
@@ -741,69 +659,6 @@
|
|
| 741 |
</item>
|
| 742 |
</layout>
|
| 743 |
</widget>
|
| 744 |
-
<widget class="QMenuBar" name="menuBar">
|
| 745 |
-
<property name="geometry">
|
| 746 |
-
<rect>
|
| 747 |
-
<x>0</x>
|
| 748 |
-
<y>0</y>
|
| 749 |
-
<width>672</width>
|
| 750 |
-
<height>24</height>
|
| 751 |
-
</rect>
|
| 752 |
-
</property>
|
| 753 |
-
<property name="font">
|
| 754 |
-
<font>
|
| 755 |
-
<family>Arial</family>
|
| 756 |
-
<pointsize>8</pointsize>
|
| 757 |
-
<weight>50</weight>
|
| 758 |
-
<italic>false</italic>
|
| 759 |
-
<bold>false</bold>
|
| 760 |
-
</font>
|
| 761 |
-
</property>
|
| 762 |
-
<widget class="QMenu" name="menuFile">
|
| 763 |
-
<property name="title">
|
| 764 |
-
<string>File</string>
|
| 765 |
-
</property>
|
| 766 |
-
<addaction name="actionExit"/>
|
| 767 |
-
<addaction name="actionTest"/>
|
| 768 |
-
</widget>
|
| 769 |
-
<widget class="QMenu" name="menuGenome">
|
| 770 |
-
<property name="title">
|
| 771 |
-
<string>Genome</string>
|
| 772 |
-
</property>
|
| 773 |
-
<addaction name="actChangeDirectory"/>
|
| 774 |
-
<addaction name="actOpenGenomeBrowser"/>
|
| 775 |
-
</widget>
|
| 776 |
-
<widget class="QMenu" name="menuTools">
|
| 777 |
-
<property name="title">
|
| 778 |
-
<string>Tools</string>
|
| 779 |
-
</property>
|
| 780 |
-
<addaction name="actOpenNCBIBLAST"/>
|
| 781 |
-
</widget>
|
| 782 |
-
<widget class="QMenu" name="menuWindow">
|
| 783 |
-
<property name="title">
|
| 784 |
-
<string>Web Links</string>
|
| 785 |
-
</property>
|
| 786 |
-
<addaction name="actOpenNCBI"/>
|
| 787 |
-
</widget>
|
| 788 |
-
<widget class="QMenu" name="menuHelp">
|
| 789 |
-
<property name="title">
|
| 790 |
-
<string>Help</string>
|
| 791 |
-
</property>
|
| 792 |
-
<addaction name="actOpenRepository"/>
|
| 793 |
-
</widget>
|
| 794 |
-
<widget class="QMenu" name="menuCASPER">
|
| 795 |
-
<property name="title">
|
| 796 |
-
<string>CASPER</string>
|
| 797 |
-
</property>
|
| 798 |
-
<addaction name="actionAbout_CASPER"/>
|
| 799 |
-
</widget>
|
| 800 |
-
<addaction name="menuCASPER"/>
|
| 801 |
-
<addaction name="menuFile"/>
|
| 802 |
-
<addaction name="menuGenome"/>
|
| 803 |
-
<addaction name="menuTools"/>
|
| 804 |
-
<addaction name="menuWindow"/>
|
| 805 |
-
<addaction name="menuHelp"/>
|
| 806 |
-
</widget>
|
| 807 |
<action name="actionNew">
|
| 808 |
<property name="text">
|
| 809 |
<string>New</string>
|
|
@@ -1011,7 +866,6 @@
|
|
| 1011 |
<tabstop>rbtnFeature</tabstop>
|
| 1012 |
<tabstop>rbtnPosition</tabstop>
|
| 1013 |
<tabstop>txtedGeneEntry</tabstop>
|
| 1014 |
-
<tabstop>pbtnFindTargets</tabstop>
|
| 1015 |
</tabstops>
|
| 1016 |
<resources/>
|
| 1017 |
<connections/>
|
|
|
|
| 1 |
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
<ui version="4.0">
|
| 3 |
<class>MainWindow</class>
|
| 4 |
+
<widget class="QWidget" name="MainWindow">
|
| 5 |
<property name="enabled">
|
| 6 |
<bool>true</bool>
|
| 7 |
</property>
|
|
|
|
| 9 |
<rect>
|
| 10 |
<x>0</x>
|
| 11 |
<y>0</y>
|
| 12 |
+
<width>960</width>
|
| 13 |
+
<height>1147</height>
|
| 14 |
</rect>
|
| 15 |
</property>
|
| 16 |
<property name="font">
|
|
|
|
| 29 |
<string notr="true"/>
|
| 30 |
</property>
|
| 31 |
<widget class="QWidget" name="centralwidget">
|
| 32 |
+
<property name="geometry">
|
| 33 |
+
<rect>
|
| 34 |
+
<x>0</x>
|
| 35 |
+
<y>0</y>
|
| 36 |
+
<width>560</width>
|
| 37 |
+
<height>522</height>
|
| 38 |
+
</rect>
|
| 39 |
+
</property>
|
| 40 |
<property name="font">
|
| 41 |
<font>
|
| 42 |
<family>Arial</family>
|
|
|
|
| 49 |
<property name="styleSheet">
|
| 50 |
<string notr="true"/>
|
| 51 |
</property>
|
| 52 |
+
<layout class="QGridLayout" name="gridContainer">
|
| 53 |
<item row="4" column="0" rowspan="2" colspan="2">
|
| 54 |
<layout class="QGridLayout" name="gridStep1Step2Step3">
|
| 55 |
<property name="sizeConstraint">
|
|
|
|
| 111 |
<height>16777215</height>
|
| 112 |
</size>
|
| 113 |
</property>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
</widget>
|
| 115 |
</item>
|
| 116 |
<item row="1" column="1">
|
|
|
|
| 136 |
<height>16777215</height>
|
| 137 |
</size>
|
| 138 |
</property>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
</widget>
|
| 140 |
</item>
|
| 141 |
<item row="1" column="0">
|
|
|
|
| 432 |
<property name="topMargin">
|
| 433 |
<number>15</number>
|
| 434 |
</property>
|
| 435 |
+
<item row="0" column="0">
|
| 436 |
+
<widget class="QRadioButton" name="rbtnFeature">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 437 |
<property name="sizePolicy">
|
| 438 |
+
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
| 439 |
<horstretch>0</horstretch>
|
| 440 |
<verstretch>0</verstretch>
|
| 441 |
</sizepolicy>
|
| 442 |
</property>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 443 |
<property name="font">
|
| 444 |
<font>
|
| 445 |
<family>Arial</family>
|
|
|
|
| 449 |
<bold>false</bold>
|
| 450 |
</font>
|
| 451 |
</property>
|
| 452 |
+
<property name="text">
|
| 453 |
+
<string>Feature</string>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 454 |
</property>
|
| 455 |
+
<property name="checked">
|
| 456 |
+
<bool>true</bool>
|
| 457 |
</property>
|
| 458 |
</widget>
|
| 459 |
</item>
|
| 460 |
+
<item row="0" column="2">
|
| 461 |
+
<widget class="QRadioButton" name="rbtnSequence">
|
| 462 |
+
<property name="text">
|
| 463 |
+
<string>Sequence</string>
|
| 464 |
</property>
|
| 465 |
+
</widget>
|
| 466 |
+
</item>
|
| 467 |
+
<item row="1" column="0" colspan="3">
|
| 468 |
+
<widget class="QPlainTextEdit" name="txtedGeneEntry">
|
| 469 |
<property name="sizePolicy">
|
| 470 |
+
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
| 471 |
<horstretch>0</horstretch>
|
| 472 |
<verstretch>0</verstretch>
|
| 473 |
</sizepolicy>
|
| 474 |
</property>
|
| 475 |
<property name="minimumSize">
|
| 476 |
<size>
|
| 477 |
+
<width>250</width>
|
| 478 |
<height>0</height>
|
| 479 |
</size>
|
| 480 |
</property>
|
|
|
|
| 484 |
<height>16777215</height>
|
| 485 |
</size>
|
| 486 |
</property>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 487 |
<property name="font">
|
| 488 |
<font>
|
| 489 |
<family>Arial</family>
|
|
|
|
| 493 |
<bold>false</bold>
|
| 494 |
</font>
|
| 495 |
</property>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 496 |
</widget>
|
| 497 |
</item>
|
| 498 |
<item row="0" column="1">
|
|
|
|
| 523 |
</property>
|
| 524 |
</widget>
|
| 525 |
</item>
|
| 526 |
+
<item row="3" column="0" colspan="3">
|
| 527 |
<layout class="QHBoxLayout" name="horizontalLayout">
|
| 528 |
<item>
|
| 529 |
+
<widget class="QPushButton" name="pbtnFindViewTargets">
|
| 530 |
<property name="sizePolicy">
|
| 531 |
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 532 |
<horstretch>0</horstretch>
|
|
|
|
| 540 |
</size>
|
| 541 |
</property>
|
| 542 |
<property name="text">
|
| 543 |
+
<string>Find Targets</string>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 544 |
</property>
|
| 545 |
</widget>
|
| 546 |
</item>
|
|
|
|
| 659 |
</item>
|
| 660 |
</layout>
|
| 661 |
</widget>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 662 |
<action name="actionNew">
|
| 663 |
<property name="text">
|
| 664 |
<string>New</string>
|
|
|
|
| 866 |
<tabstop>rbtnFeature</tabstop>
|
| 867 |
<tabstop>rbtnPosition</tabstop>
|
| 868 |
<tabstop>txtedGeneEntry</tabstop>
|
|
|
|
| 869 |
</tabstops>
|
| 870 |
<resources/>
|
| 871 |
<connections/>
|
|
@@ -1,946 +0,0 @@
|
|
| 1 |
-
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
-
<ui version="4.0">
|
| 3 |
-
<class>MainWindow</class>
|
| 4 |
-
<widget class="QWidget" name="MainWindow">
|
| 5 |
-
<property name="enabled">
|
| 6 |
-
<bool>true</bool>
|
| 7 |
-
</property>
|
| 8 |
-
<property name="geometry">
|
| 9 |
-
<rect>
|
| 10 |
-
<x>0</x>
|
| 11 |
-
<y>0</y>
|
| 12 |
-
<width>672</width>
|
| 13 |
-
<height>788</height>
|
| 14 |
-
</rect>
|
| 15 |
-
</property>
|
| 16 |
-
<property name="font">
|
| 17 |
-
<font>
|
| 18 |
-
<family>Arial</family>
|
| 19 |
-
<pointsize>12</pointsize>
|
| 20 |
-
<italic>false</italic>
|
| 21 |
-
<bold>false</bold>
|
| 22 |
-
</font>
|
| 23 |
-
</property>
|
| 24 |
-
<property name="windowTitle">
|
| 25 |
-
<string>CASPER</string>
|
| 26 |
-
</property>
|
| 27 |
-
<property name="styleSheet">
|
| 28 |
-
<string notr="true"/>
|
| 29 |
-
</property>
|
| 30 |
-
<widget class="QWidget" name="centralwidget">
|
| 31 |
-
<property name="geometry">
|
| 32 |
-
<rect>
|
| 33 |
-
<x>0</x>
|
| 34 |
-
<y>0</y>
|
| 35 |
-
<width>552</width>
|
| 36 |
-
<height>522</height>
|
| 37 |
-
</rect>
|
| 38 |
-
</property>
|
| 39 |
-
<property name="font">
|
| 40 |
-
<font>
|
| 41 |
-
<family>Arial</family>
|
| 42 |
-
<pointsize>12</pointsize>
|
| 43 |
-
<italic>false</italic>
|
| 44 |
-
<bold>false</bold>
|
| 45 |
-
</font>
|
| 46 |
-
</property>
|
| 47 |
-
<property name="styleSheet">
|
| 48 |
-
<string notr="true"/>
|
| 49 |
-
</property>
|
| 50 |
-
<layout class="QGridLayout" name="gridContainer">
|
| 51 |
-
<item row="4" column="0" rowspan="2" colspan="2">
|
| 52 |
-
<layout class="QGridLayout" name="gridStep1Step2Step3">
|
| 53 |
-
<property name="sizeConstraint">
|
| 54 |
-
<enum>QLayout::SetDefaultConstraint</enum>
|
| 55 |
-
</property>
|
| 56 |
-
<item row="0" column="0" rowspan="2">
|
| 57 |
-
<widget class="QGroupBox" name="grpStep1">
|
| 58 |
-
<property name="sizePolicy">
|
| 59 |
-
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
| 60 |
-
<horstretch>0</horstretch>
|
| 61 |
-
<verstretch>0</verstretch>
|
| 62 |
-
</sizepolicy>
|
| 63 |
-
</property>
|
| 64 |
-
<property name="minimumSize">
|
| 65 |
-
<size>
|
| 66 |
-
<width>0</width>
|
| 67 |
-
<height>0</height>
|
| 68 |
-
</size>
|
| 69 |
-
</property>
|
| 70 |
-
<property name="maximumSize">
|
| 71 |
-
<size>
|
| 72 |
-
<width>16777215</width>
|
| 73 |
-
<height>16777215</height>
|
| 74 |
-
</size>
|
| 75 |
-
</property>
|
| 76 |
-
<property name="font">
|
| 77 |
-
<font>
|
| 78 |
-
<family>Arial</family>
|
| 79 |
-
<pointsize>12</pointsize>
|
| 80 |
-
<italic>false</italic>
|
| 81 |
-
<bold>false</bold>
|
| 82 |
-
</font>
|
| 83 |
-
</property>
|
| 84 |
-
<property name="styleSheet">
|
| 85 |
-
<string notr="true"/>
|
| 86 |
-
</property>
|
| 87 |
-
<property name="title">
|
| 88 |
-
<string>Step 1: Select Organism and Endonuclease</string>
|
| 89 |
-
</property>
|
| 90 |
-
<layout class="QGridLayout" name="gridLayout">
|
| 91 |
-
<item row="2" column="1">
|
| 92 |
-
<widget class="QComboBox" name="cmbEndonuclease">
|
| 93 |
-
<property name="sizePolicy">
|
| 94 |
-
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 95 |
-
<horstretch>0</horstretch>
|
| 96 |
-
<verstretch>0</verstretch>
|
| 97 |
-
</sizepolicy>
|
| 98 |
-
</property>
|
| 99 |
-
<property name="minimumSize">
|
| 100 |
-
<size>
|
| 101 |
-
<width>0</width>
|
| 102 |
-
<height>0</height>
|
| 103 |
-
</size>
|
| 104 |
-
</property>
|
| 105 |
-
<property name="maximumSize">
|
| 106 |
-
<size>
|
| 107 |
-
<width>16777215</width>
|
| 108 |
-
<height>16777215</height>
|
| 109 |
-
</size>
|
| 110 |
-
</property>
|
| 111 |
-
<property name="font">
|
| 112 |
-
<font>
|
| 113 |
-
<family>Arial</family>
|
| 114 |
-
<pointsize>8</pointsize>
|
| 115 |
-
<italic>false</italic>
|
| 116 |
-
<bold>false</bold>
|
| 117 |
-
</font>
|
| 118 |
-
</property>
|
| 119 |
-
</widget>
|
| 120 |
-
</item>
|
| 121 |
-
<item row="1" column="1">
|
| 122 |
-
<widget class="QComboBox" name="cmbOrganism">
|
| 123 |
-
<property name="enabled">
|
| 124 |
-
<bool>true</bool>
|
| 125 |
-
</property>
|
| 126 |
-
<property name="sizePolicy">
|
| 127 |
-
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 128 |
-
<horstretch>0</horstretch>
|
| 129 |
-
<verstretch>0</verstretch>
|
| 130 |
-
</sizepolicy>
|
| 131 |
-
</property>
|
| 132 |
-
<property name="minimumSize">
|
| 133 |
-
<size>
|
| 134 |
-
<width>0</width>
|
| 135 |
-
<height>0</height>
|
| 136 |
-
</size>
|
| 137 |
-
</property>
|
| 138 |
-
<property name="maximumSize">
|
| 139 |
-
<size>
|
| 140 |
-
<width>16777215</width>
|
| 141 |
-
<height>16777215</height>
|
| 142 |
-
</size>
|
| 143 |
-
</property>
|
| 144 |
-
<property name="font">
|
| 145 |
-
<font>
|
| 146 |
-
<family>Arial</family>
|
| 147 |
-
<pointsize>8</pointsize>
|
| 148 |
-
<italic>false</italic>
|
| 149 |
-
<bold>false</bold>
|
| 150 |
-
</font>
|
| 151 |
-
</property>
|
| 152 |
-
</widget>
|
| 153 |
-
</item>
|
| 154 |
-
<item row="1" column="0">
|
| 155 |
-
<widget class="QLabel" name="txtOrganismName">
|
| 156 |
-
<property name="sizePolicy">
|
| 157 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 158 |
-
<horstretch>0</horstretch>
|
| 159 |
-
<verstretch>0</verstretch>
|
| 160 |
-
</sizepolicy>
|
| 161 |
-
</property>
|
| 162 |
-
<property name="minimumSize">
|
| 163 |
-
<size>
|
| 164 |
-
<width>0</width>
|
| 165 |
-
<height>0</height>
|
| 166 |
-
</size>
|
| 167 |
-
</property>
|
| 168 |
-
<property name="maximumSize">
|
| 169 |
-
<size>
|
| 170 |
-
<width>16777215</width>
|
| 171 |
-
<height>16777215</height>
|
| 172 |
-
</size>
|
| 173 |
-
</property>
|
| 174 |
-
<property name="font">
|
| 175 |
-
<font>
|
| 176 |
-
<family>Arial</family>
|
| 177 |
-
<pointsize>12</pointsize>
|
| 178 |
-
<italic>false</italic>
|
| 179 |
-
<bold>false</bold>
|
| 180 |
-
</font>
|
| 181 |
-
</property>
|
| 182 |
-
<property name="styleSheet">
|
| 183 |
-
<string notr="true"/>
|
| 184 |
-
</property>
|
| 185 |
-
<property name="text">
|
| 186 |
-
<string>Organism Name:</string>
|
| 187 |
-
</property>
|
| 188 |
-
</widget>
|
| 189 |
-
</item>
|
| 190 |
-
<item row="2" column="0">
|
| 191 |
-
<widget class="QLabel" name="txtEndonuclease">
|
| 192 |
-
<property name="sizePolicy">
|
| 193 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 194 |
-
<horstretch>0</horstretch>
|
| 195 |
-
<verstretch>0</verstretch>
|
| 196 |
-
</sizepolicy>
|
| 197 |
-
</property>
|
| 198 |
-
<property name="minimumSize">
|
| 199 |
-
<size>
|
| 200 |
-
<width>0</width>
|
| 201 |
-
<height>0</height>
|
| 202 |
-
</size>
|
| 203 |
-
</property>
|
| 204 |
-
<property name="maximumSize">
|
| 205 |
-
<size>
|
| 206 |
-
<width>16777215</width>
|
| 207 |
-
<height>16777215</height>
|
| 208 |
-
</size>
|
| 209 |
-
</property>
|
| 210 |
-
<property name="font">
|
| 211 |
-
<font>
|
| 212 |
-
<family>Arial</family>
|
| 213 |
-
<pointsize>12</pointsize>
|
| 214 |
-
<italic>false</italic>
|
| 215 |
-
<bold>false</bold>
|
| 216 |
-
</font>
|
| 217 |
-
</property>
|
| 218 |
-
<property name="styleSheet">
|
| 219 |
-
<string notr="true"/>
|
| 220 |
-
</property>
|
| 221 |
-
<property name="text">
|
| 222 |
-
<string>Endonuclease:</string>
|
| 223 |
-
</property>
|
| 224 |
-
</widget>
|
| 225 |
-
</item>
|
| 226 |
-
</layout>
|
| 227 |
-
</widget>
|
| 228 |
-
</item>
|
| 229 |
-
<item row="2" column="0" rowspan="2">
|
| 230 |
-
<widget class="QGroupBox" name="grpStep2">
|
| 231 |
-
<property name="sizePolicy">
|
| 232 |
-
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
| 233 |
-
<horstretch>0</horstretch>
|
| 234 |
-
<verstretch>0</verstretch>
|
| 235 |
-
</sizepolicy>
|
| 236 |
-
</property>
|
| 237 |
-
<property name="minimumSize">
|
| 238 |
-
<size>
|
| 239 |
-
<width>0</width>
|
| 240 |
-
<height>0</height>
|
| 241 |
-
</size>
|
| 242 |
-
</property>
|
| 243 |
-
<property name="maximumSize">
|
| 244 |
-
<size>
|
| 245 |
-
<width>16777215</width>
|
| 246 |
-
<height>16777215</height>
|
| 247 |
-
</size>
|
| 248 |
-
</property>
|
| 249 |
-
<property name="font">
|
| 250 |
-
<font>
|
| 251 |
-
<family>Arial</family>
|
| 252 |
-
<pointsize>12</pointsize>
|
| 253 |
-
<italic>false</italic>
|
| 254 |
-
<bold>false</bold>
|
| 255 |
-
</font>
|
| 256 |
-
</property>
|
| 257 |
-
<property name="styleSheet">
|
| 258 |
-
<string notr="true"/>
|
| 259 |
-
</property>
|
| 260 |
-
<property name="title">
|
| 261 |
-
<string>Step 2: Choose Annotation File</string>
|
| 262 |
-
</property>
|
| 263 |
-
<layout class="QGridLayout" name="gridLayout_5">
|
| 264 |
-
<property name="topMargin">
|
| 265 |
-
<number>15</number>
|
| 266 |
-
</property>
|
| 267 |
-
<item row="1" column="1" colspan="2">
|
| 268 |
-
<widget class="QLabel" name="txtNCBIFileSearch">
|
| 269 |
-
<property name="sizePolicy">
|
| 270 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 271 |
-
<horstretch>0</horstretch>
|
| 272 |
-
<verstretch>0</verstretch>
|
| 273 |
-
</sizepolicy>
|
| 274 |
-
</property>
|
| 275 |
-
<property name="maximumSize">
|
| 276 |
-
<size>
|
| 277 |
-
<width>16777215</width>
|
| 278 |
-
<height>16777215</height>
|
| 279 |
-
</size>
|
| 280 |
-
</property>
|
| 281 |
-
<property name="font">
|
| 282 |
-
<font>
|
| 283 |
-
<family>Arial</family>
|
| 284 |
-
<pointsize>12</pointsize>
|
| 285 |
-
<italic>false</italic>
|
| 286 |
-
<bold>false</bold>
|
| 287 |
-
</font>
|
| 288 |
-
</property>
|
| 289 |
-
<property name="styleSheet">
|
| 290 |
-
<string notr="true"/>
|
| 291 |
-
</property>
|
| 292 |
-
<property name="text">
|
| 293 |
-
<string>Select local file or download from NCBI:</string>
|
| 294 |
-
</property>
|
| 295 |
-
<property name="alignment">
|
| 296 |
-
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
|
| 297 |
-
</property>
|
| 298 |
-
</widget>
|
| 299 |
-
</item>
|
| 300 |
-
<item row="3" column="1" colspan="2">
|
| 301 |
-
<widget class="QLabel" name="txtLocalAnnotationFiles">
|
| 302 |
-
<property name="sizePolicy">
|
| 303 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 304 |
-
<horstretch>0</horstretch>
|
| 305 |
-
<verstretch>0</verstretch>
|
| 306 |
-
</sizepolicy>
|
| 307 |
-
</property>
|
| 308 |
-
<property name="minimumSize">
|
| 309 |
-
<size>
|
| 310 |
-
<width>0</width>
|
| 311 |
-
<height>0</height>
|
| 312 |
-
</size>
|
| 313 |
-
</property>
|
| 314 |
-
<property name="maximumSize">
|
| 315 |
-
<size>
|
| 316 |
-
<width>16777215</width>
|
| 317 |
-
<height>16777215</height>
|
| 318 |
-
</size>
|
| 319 |
-
</property>
|
| 320 |
-
<property name="font">
|
| 321 |
-
<font>
|
| 322 |
-
<family>Arial</family>
|
| 323 |
-
<pointsize>12</pointsize>
|
| 324 |
-
<italic>false</italic>
|
| 325 |
-
<bold>false</bold>
|
| 326 |
-
</font>
|
| 327 |
-
</property>
|
| 328 |
-
<property name="text">
|
| 329 |
-
<string>Local Annotation Files:</string>
|
| 330 |
-
</property>
|
| 331 |
-
<property name="alignment">
|
| 332 |
-
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
|
| 333 |
-
</property>
|
| 334 |
-
</widget>
|
| 335 |
-
</item>
|
| 336 |
-
<item row="4" column="1" colspan="2">
|
| 337 |
-
<widget class="QComboBox" name="cmbLocalAnnotationFiles">
|
| 338 |
-
<property name="sizePolicy">
|
| 339 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 340 |
-
<horstretch>0</horstretch>
|
| 341 |
-
<verstretch>0</verstretch>
|
| 342 |
-
</sizepolicy>
|
| 343 |
-
</property>
|
| 344 |
-
<property name="minimumSize">
|
| 345 |
-
<size>
|
| 346 |
-
<width>0</width>
|
| 347 |
-
<height>0</height>
|
| 348 |
-
</size>
|
| 349 |
-
</property>
|
| 350 |
-
<property name="maximumSize">
|
| 351 |
-
<size>
|
| 352 |
-
<width>16777215</width>
|
| 353 |
-
<height>16777215</height>
|
| 354 |
-
</size>
|
| 355 |
-
</property>
|
| 356 |
-
<property name="font">
|
| 357 |
-
<font>
|
| 358 |
-
<family>Arial</family>
|
| 359 |
-
<pointsize>12</pointsize>
|
| 360 |
-
<italic>false</italic>
|
| 361 |
-
<bold>false</bold>
|
| 362 |
-
</font>
|
| 363 |
-
</property>
|
| 364 |
-
</widget>
|
| 365 |
-
</item>
|
| 366 |
-
<item row="2" column="1" colspan="2">
|
| 367 |
-
<widget class="QPushButton" name="pbtnNCBIFileSearch">
|
| 368 |
-
<property name="sizePolicy">
|
| 369 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 370 |
-
<horstretch>0</horstretch>
|
| 371 |
-
<verstretch>0</verstretch>
|
| 372 |
-
</sizepolicy>
|
| 373 |
-
</property>
|
| 374 |
-
<property name="minimumSize">
|
| 375 |
-
<size>
|
| 376 |
-
<width>125</width>
|
| 377 |
-
<height>0</height>
|
| 378 |
-
</size>
|
| 379 |
-
</property>
|
| 380 |
-
<property name="maximumSize">
|
| 381 |
-
<size>
|
| 382 |
-
<width>16777215</width>
|
| 383 |
-
<height>16777215</height>
|
| 384 |
-
</size>
|
| 385 |
-
</property>
|
| 386 |
-
<property name="font">
|
| 387 |
-
<font>
|
| 388 |
-
<family>Arial</family>
|
| 389 |
-
<pointsize>12</pointsize>
|
| 390 |
-
<italic>false</italic>
|
| 391 |
-
<bold>false</bold>
|
| 392 |
-
</font>
|
| 393 |
-
</property>
|
| 394 |
-
<property name="text">
|
| 395 |
-
<string>NCBI File Search</string>
|
| 396 |
-
</property>
|
| 397 |
-
</widget>
|
| 398 |
-
</item>
|
| 399 |
-
</layout>
|
| 400 |
-
</widget>
|
| 401 |
-
</item>
|
| 402 |
-
<item row="0" column="1" rowspan="4">
|
| 403 |
-
<widget class="QGroupBox" name="grpStep3">
|
| 404 |
-
<property name="sizePolicy">
|
| 405 |
-
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
| 406 |
-
<horstretch>0</horstretch>
|
| 407 |
-
<verstretch>0</verstretch>
|
| 408 |
-
</sizepolicy>
|
| 409 |
-
</property>
|
| 410 |
-
<property name="minimumSize">
|
| 411 |
-
<size>
|
| 412 |
-
<width>0</width>
|
| 413 |
-
<height>0</height>
|
| 414 |
-
</size>
|
| 415 |
-
</property>
|
| 416 |
-
<property name="maximumSize">
|
| 417 |
-
<size>
|
| 418 |
-
<width>16777215</width>
|
| 419 |
-
<height>16777215</height>
|
| 420 |
-
</size>
|
| 421 |
-
</property>
|
| 422 |
-
<property name="font">
|
| 423 |
-
<font>
|
| 424 |
-
<family>Arial</family>
|
| 425 |
-
<pointsize>12</pointsize>
|
| 426 |
-
<italic>false</italic>
|
| 427 |
-
<bold>false</bold>
|
| 428 |
-
</font>
|
| 429 |
-
</property>
|
| 430 |
-
<property name="styleSheet">
|
| 431 |
-
<string notr="true"/>
|
| 432 |
-
</property>
|
| 433 |
-
<property name="title">
|
| 434 |
-
<string>Step 3: Search and Find Targets</string>
|
| 435 |
-
</property>
|
| 436 |
-
<layout class="QGridLayout" name="gridLayout_4">
|
| 437 |
-
<property name="topMargin">
|
| 438 |
-
<number>15</number>
|
| 439 |
-
</property>
|
| 440 |
-
<item row="0" column="2">
|
| 441 |
-
<widget class="QRadioButton" name="rbtnSequence">
|
| 442 |
-
<property name="text">
|
| 443 |
-
<string>Sequence</string>
|
| 444 |
-
</property>
|
| 445 |
-
</widget>
|
| 446 |
-
</item>
|
| 447 |
-
<item row="1" column="0" colspan="3">
|
| 448 |
-
<widget class="QPlainTextEdit" name="txtedGeneEntry">
|
| 449 |
-
<property name="sizePolicy">
|
| 450 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
| 451 |
-
<horstretch>0</horstretch>
|
| 452 |
-
<verstretch>0</verstretch>
|
| 453 |
-
</sizepolicy>
|
| 454 |
-
</property>
|
| 455 |
-
<property name="minimumSize">
|
| 456 |
-
<size>
|
| 457 |
-
<width>250</width>
|
| 458 |
-
<height>0</height>
|
| 459 |
-
</size>
|
| 460 |
-
</property>
|
| 461 |
-
<property name="maximumSize">
|
| 462 |
-
<size>
|
| 463 |
-
<width>16777215</width>
|
| 464 |
-
<height>16777215</height>
|
| 465 |
-
</size>
|
| 466 |
-
</property>
|
| 467 |
-
<property name="font">
|
| 468 |
-
<font>
|
| 469 |
-
<family>Arial</family>
|
| 470 |
-
<pointsize>12</pointsize>
|
| 471 |
-
<italic>false</italic>
|
| 472 |
-
<bold>false</bold>
|
| 473 |
-
</font>
|
| 474 |
-
</property>
|
| 475 |
-
</widget>
|
| 476 |
-
</item>
|
| 477 |
-
<item row="4" column="0" colspan="3">
|
| 478 |
-
<widget class="QProgressBar" name="progBarFindTargets">
|
| 479 |
-
<property name="sizePolicy">
|
| 480 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 481 |
-
<horstretch>0</horstretch>
|
| 482 |
-
<verstretch>0</verstretch>
|
| 483 |
-
</sizepolicy>
|
| 484 |
-
</property>
|
| 485 |
-
<property name="minimumSize">
|
| 486 |
-
<size>
|
| 487 |
-
<width>0</width>
|
| 488 |
-
<height>0</height>
|
| 489 |
-
</size>
|
| 490 |
-
</property>
|
| 491 |
-
<property name="value">
|
| 492 |
-
<number>24</number>
|
| 493 |
-
</property>
|
| 494 |
-
</widget>
|
| 495 |
-
</item>
|
| 496 |
-
<item row="2" column="0" colspan="3">
|
| 497 |
-
<widget class="QPushButton" name="pbtnFindTargets">
|
| 498 |
-
<property name="enabled">
|
| 499 |
-
<bool>true</bool>
|
| 500 |
-
</property>
|
| 501 |
-
<property name="sizePolicy">
|
| 502 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 503 |
-
<horstretch>0</horstretch>
|
| 504 |
-
<verstretch>0</verstretch>
|
| 505 |
-
</sizepolicy>
|
| 506 |
-
</property>
|
| 507 |
-
<property name="minimumSize">
|
| 508 |
-
<size>
|
| 509 |
-
<width>0</width>
|
| 510 |
-
<height>0</height>
|
| 511 |
-
</size>
|
| 512 |
-
</property>
|
| 513 |
-
<property name="maximumSize">
|
| 514 |
-
<size>
|
| 515 |
-
<width>16777215</width>
|
| 516 |
-
<height>16777215</height>
|
| 517 |
-
</size>
|
| 518 |
-
</property>
|
| 519 |
-
<property name="text">
|
| 520 |
-
<string>Find Targets</string>
|
| 521 |
-
</property>
|
| 522 |
-
</widget>
|
| 523 |
-
</item>
|
| 524 |
-
<item row="0" column="0">
|
| 525 |
-
<widget class="QRadioButton" name="rbtnFeature">
|
| 526 |
-
<property name="sizePolicy">
|
| 527 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
| 528 |
-
<horstretch>0</horstretch>
|
| 529 |
-
<verstretch>0</verstretch>
|
| 530 |
-
</sizepolicy>
|
| 531 |
-
</property>
|
| 532 |
-
<property name="font">
|
| 533 |
-
<font>
|
| 534 |
-
<family>Arial</family>
|
| 535 |
-
<pointsize>12</pointsize>
|
| 536 |
-
<italic>false</italic>
|
| 537 |
-
<bold>false</bold>
|
| 538 |
-
</font>
|
| 539 |
-
</property>
|
| 540 |
-
<property name="text">
|
| 541 |
-
<string>Feature</string>
|
| 542 |
-
</property>
|
| 543 |
-
<property name="checked">
|
| 544 |
-
<bool>true</bool>
|
| 545 |
-
</property>
|
| 546 |
-
</widget>
|
| 547 |
-
</item>
|
| 548 |
-
<item row="0" column="1">
|
| 549 |
-
<widget class="QRadioButton" name="rbtnPosition">
|
| 550 |
-
<property name="sizePolicy">
|
| 551 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
| 552 |
-
<horstretch>0</horstretch>
|
| 553 |
-
<verstretch>0</verstretch>
|
| 554 |
-
</sizepolicy>
|
| 555 |
-
</property>
|
| 556 |
-
<property name="font">
|
| 557 |
-
<font>
|
| 558 |
-
<family>Arial</family>
|
| 559 |
-
<pointsize>12</pointsize>
|
| 560 |
-
<italic>false</italic>
|
| 561 |
-
<bold>false</bold>
|
| 562 |
-
</font>
|
| 563 |
-
</property>
|
| 564 |
-
<property name="text">
|
| 565 |
-
<string>Position</string>
|
| 566 |
-
</property>
|
| 567 |
-
<property name="iconSize">
|
| 568 |
-
<size>
|
| 569 |
-
<width>16</width>
|
| 570 |
-
<height>16</height>
|
| 571 |
-
</size>
|
| 572 |
-
</property>
|
| 573 |
-
</widget>
|
| 574 |
-
</item>
|
| 575 |
-
<item row="5" column="0" colspan="3">
|
| 576 |
-
<layout class="QHBoxLayout" name="horizontalLayout">
|
| 577 |
-
<item>
|
| 578 |
-
<widget class="QPushButton" name="pbtnViewTargets">
|
| 579 |
-
<property name="sizePolicy">
|
| 580 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 581 |
-
<horstretch>0</horstretch>
|
| 582 |
-
<verstretch>0</verstretch>
|
| 583 |
-
</sizepolicy>
|
| 584 |
-
</property>
|
| 585 |
-
<property name="minimumSize">
|
| 586 |
-
<size>
|
| 587 |
-
<width>0</width>
|
| 588 |
-
<height>0</height>
|
| 589 |
-
</size>
|
| 590 |
-
</property>
|
| 591 |
-
<property name="text">
|
| 592 |
-
<string>View Targets</string>
|
| 593 |
-
</property>
|
| 594 |
-
</widget>
|
| 595 |
-
</item>
|
| 596 |
-
<item>
|
| 597 |
-
<widget class="QPushButton" name="pbtnGenerateLibrary">
|
| 598 |
-
<property name="sizePolicy">
|
| 599 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 600 |
-
<horstretch>0</horstretch>
|
| 601 |
-
<verstretch>0</verstretch>
|
| 602 |
-
</sizepolicy>
|
| 603 |
-
</property>
|
| 604 |
-
<property name="minimumSize">
|
| 605 |
-
<size>
|
| 606 |
-
<width>0</width>
|
| 607 |
-
<height>0</height>
|
| 608 |
-
</size>
|
| 609 |
-
</property>
|
| 610 |
-
<property name="maximumSize">
|
| 611 |
-
<size>
|
| 612 |
-
<width>16777215</width>
|
| 613 |
-
<height>16777215</height>
|
| 614 |
-
</size>
|
| 615 |
-
</property>
|
| 616 |
-
<property name="text">
|
| 617 |
-
<string>Generate Library</string>
|
| 618 |
-
</property>
|
| 619 |
-
</widget>
|
| 620 |
-
</item>
|
| 621 |
-
</layout>
|
| 622 |
-
</item>
|
| 623 |
-
</layout>
|
| 624 |
-
</widget>
|
| 625 |
-
</item>
|
| 626 |
-
</layout>
|
| 627 |
-
</item>
|
| 628 |
-
<item row="0" column="0">
|
| 629 |
-
<widget class="QLabel" name="lblWindowHeading">
|
| 630 |
-
<property name="sizePolicy">
|
| 631 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 632 |
-
<horstretch>0</horstretch>
|
| 633 |
-
<verstretch>0</verstretch>
|
| 634 |
-
</sizepolicy>
|
| 635 |
-
</property>
|
| 636 |
-
<property name="minimumSize">
|
| 637 |
-
<size>
|
| 638 |
-
<width>0</width>
|
| 639 |
-
<height>0</height>
|
| 640 |
-
</size>
|
| 641 |
-
</property>
|
| 642 |
-
<property name="maximumSize">
|
| 643 |
-
<size>
|
| 644 |
-
<width>16777215</width>
|
| 645 |
-
<height>16777215</height>
|
| 646 |
-
</size>
|
| 647 |
-
</property>
|
| 648 |
-
<property name="font">
|
| 649 |
-
<font>
|
| 650 |
-
<family>Arial</family>
|
| 651 |
-
<pointsize>12</pointsize>
|
| 652 |
-
<italic>false</italic>
|
| 653 |
-
<bold>true</bold>
|
| 654 |
-
</font>
|
| 655 |
-
</property>
|
| 656 |
-
<property name="styleSheet">
|
| 657 |
-
<string notr="true"/>
|
| 658 |
-
</property>
|
| 659 |
-
<property name="text">
|
| 660 |
-
<string>CASPER</string>
|
| 661 |
-
</property>
|
| 662 |
-
</widget>
|
| 663 |
-
</item>
|
| 664 |
-
<item row="0" column="1">
|
| 665 |
-
<widget class="QLabel" name="pbtnThemeToggle">
|
| 666 |
-
<property name="minimumSize">
|
| 667 |
-
<size>
|
| 668 |
-
<width>50</width>
|
| 669 |
-
<height>0</height>
|
| 670 |
-
</size>
|
| 671 |
-
</property>
|
| 672 |
-
<property name="maximumSize">
|
| 673 |
-
<size>
|
| 674 |
-
<width>50</width>
|
| 675 |
-
<height>16777215</height>
|
| 676 |
-
</size>
|
| 677 |
-
</property>
|
| 678 |
-
<property name="text">
|
| 679 |
-
<string/>
|
| 680 |
-
</property>
|
| 681 |
-
</widget>
|
| 682 |
-
</item>
|
| 683 |
-
<item row="2" column="0" colspan="2">
|
| 684 |
-
<widget class="QGroupBox" name="grpNavigationMenu">
|
| 685 |
-
<property name="sizePolicy">
|
| 686 |
-
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
|
| 687 |
-
<horstretch>0</horstretch>
|
| 688 |
-
<verstretch>0</verstretch>
|
| 689 |
-
</sizepolicy>
|
| 690 |
-
</property>
|
| 691 |
-
<property name="title">
|
| 692 |
-
<string>CASPER Navigation</string>
|
| 693 |
-
</property>
|
| 694 |
-
<layout class="QGridLayout" name="gridLayout_2">
|
| 695 |
-
<item row="1" column="1">
|
| 696 |
-
<widget class="QPushButton" name="pbtnPopulationAnalysis">
|
| 697 |
-
<property name="text">
|
| 698 |
-
<string>Population Analysis</string>
|
| 699 |
-
</property>
|
| 700 |
-
</widget>
|
| 701 |
-
</item>
|
| 702 |
-
<item row="0" column="1">
|
| 703 |
-
<widget class="QPushButton" name="pbtnNewEndonuclease">
|
| 704 |
-
<property name="text">
|
| 705 |
-
<string>Define New Endonuclease</string>
|
| 706 |
-
</property>
|
| 707 |
-
</widget>
|
| 708 |
-
</item>
|
| 709 |
-
<item row="1" column="0">
|
| 710 |
-
<widget class="QPushButton" name="pbtnMultitargetingAnalysis">
|
| 711 |
-
<property name="text">
|
| 712 |
-
<string>Multitargeting Analysis</string>
|
| 713 |
-
</property>
|
| 714 |
-
</widget>
|
| 715 |
-
</item>
|
| 716 |
-
<item row="2" column="0">
|
| 717 |
-
<widget class="QPushButton" name="pbtnCombineFiles">
|
| 718 |
-
<property name="text">
|
| 719 |
-
<string>Combine Files</string>
|
| 720 |
-
</property>
|
| 721 |
-
</widget>
|
| 722 |
-
</item>
|
| 723 |
-
<item row="0" column="0">
|
| 724 |
-
<widget class="QPushButton" name="pbtnNewGenome">
|
| 725 |
-
<property name="text">
|
| 726 |
-
<string>Analyze New Genome</string>
|
| 727 |
-
</property>
|
| 728 |
-
</widget>
|
| 729 |
-
</item>
|
| 730 |
-
</layout>
|
| 731 |
-
</widget>
|
| 732 |
-
</item>
|
| 733 |
-
</layout>
|
| 734 |
-
</widget>
|
| 735 |
-
<action name="actionNew">
|
| 736 |
-
<property name="text">
|
| 737 |
-
<string>New</string>
|
| 738 |
-
</property>
|
| 739 |
-
</action>
|
| 740 |
-
<action name="actionOpen">
|
| 741 |
-
<property name="text">
|
| 742 |
-
<string>Open</string>
|
| 743 |
-
</property>
|
| 744 |
-
</action>
|
| 745 |
-
<action name="actionOpen_Recent">
|
| 746 |
-
<property name="text">
|
| 747 |
-
<string>Open Recent...</string>
|
| 748 |
-
</property>
|
| 749 |
-
</action>
|
| 750 |
-
<action name="actionSave">
|
| 751 |
-
<property name="text">
|
| 752 |
-
<string>Save</string>
|
| 753 |
-
</property>
|
| 754 |
-
</action>
|
| 755 |
-
<action name="actionExit">
|
| 756 |
-
<property name="checkable">
|
| 757 |
-
<bool>false</bool>
|
| 758 |
-
</property>
|
| 759 |
-
<property name="text">
|
| 760 |
-
<string>Exit</string>
|
| 761 |
-
</property>
|
| 762 |
-
<property name="font">
|
| 763 |
-
<font>
|
| 764 |
-
<family>Arial</family>
|
| 765 |
-
</font>
|
| 766 |
-
</property>
|
| 767 |
-
</action>
|
| 768 |
-
<action name="actionUndo">
|
| 769 |
-
<property name="text">
|
| 770 |
-
<string>Undo</string>
|
| 771 |
-
</property>
|
| 772 |
-
</action>
|
| 773 |
-
<action name="actionCut">
|
| 774 |
-
<property name="text">
|
| 775 |
-
<string>Cut</string>
|
| 776 |
-
</property>
|
| 777 |
-
</action>
|
| 778 |
-
<action name="actionCopy">
|
| 779 |
-
<property name="text">
|
| 780 |
-
<string>Copy</string>
|
| 781 |
-
</property>
|
| 782 |
-
</action>
|
| 783 |
-
<action name="actionPaste">
|
| 784 |
-
<property name="text">
|
| 785 |
-
<string>Paste</string>
|
| 786 |
-
</property>
|
| 787 |
-
</action>
|
| 788 |
-
<action name="actionCut_Rev_Com">
|
| 789 |
-
<property name="text">
|
| 790 |
-
<string>Cut Rev-Com</string>
|
| 791 |
-
</property>
|
| 792 |
-
</action>
|
| 793 |
-
<action name="actionCopy_Rev_Com">
|
| 794 |
-
<property name="text">
|
| 795 |
-
<string>Copy Rev-Com</string>
|
| 796 |
-
</property>
|
| 797 |
-
</action>
|
| 798 |
-
<action name="actionPaste_Rev_Com">
|
| 799 |
-
<property name="text">
|
| 800 |
-
<string>Paste Rev-Com</string>
|
| 801 |
-
</property>
|
| 802 |
-
</action>
|
| 803 |
-
<action name="actionUpload_New_Genome">
|
| 804 |
-
<property name="text">
|
| 805 |
-
<string>Analyze New Genome</string>
|
| 806 |
-
</property>
|
| 807 |
-
<property name="font">
|
| 808 |
-
<font>
|
| 809 |
-
<family>Arial</family>
|
| 810 |
-
</font>
|
| 811 |
-
</property>
|
| 812 |
-
</action>
|
| 813 |
-
<action name="actionUpload_New_Endonuclease">
|
| 814 |
-
<property name="text">
|
| 815 |
-
<string>Define New Endonuclease</string>
|
| 816 |
-
</property>
|
| 817 |
-
<property name="font">
|
| 818 |
-
<font>
|
| 819 |
-
<family>Arial</family>
|
| 820 |
-
</font>
|
| 821 |
-
</property>
|
| 822 |
-
</action>
|
| 823 |
-
<action name="actOpenGenomeBrowser">
|
| 824 |
-
<property name="text">
|
| 825 |
-
<string>Open Genome Browser</string>
|
| 826 |
-
</property>
|
| 827 |
-
<property name="font">
|
| 828 |
-
<font>
|
| 829 |
-
<family>Arial</family>
|
| 830 |
-
</font>
|
| 831 |
-
</property>
|
| 832 |
-
</action>
|
| 833 |
-
<action name="actChangeDirectory">
|
| 834 |
-
<property name="text">
|
| 835 |
-
<string>Change Database Directory</string>
|
| 836 |
-
</property>
|
| 837 |
-
<property name="font">
|
| 838 |
-
<font>
|
| 839 |
-
<family>Arial</family>
|
| 840 |
-
</font>
|
| 841 |
-
</property>
|
| 842 |
-
</action>
|
| 843 |
-
<action name="actOpenNCBIBLAST">
|
| 844 |
-
<property name="text">
|
| 845 |
-
<string>NCBI BLAST</string>
|
| 846 |
-
</property>
|
| 847 |
-
</action>
|
| 848 |
-
<action name="actionExcel_csv">
|
| 849 |
-
<property name="text">
|
| 850 |
-
<string>Excel (.csv)</string>
|
| 851 |
-
</property>
|
| 852 |
-
</action>
|
| 853 |
-
<action name="actionIDT_Order">
|
| 854 |
-
<property name="text">
|
| 855 |
-
<string>IDT Order</string>
|
| 856 |
-
</property>
|
| 857 |
-
</action>
|
| 858 |
-
<action name="actionMultitargeting">
|
| 859 |
-
<property name="text">
|
| 860 |
-
<string>Multitargeting Analysis</string>
|
| 861 |
-
</property>
|
| 862 |
-
<property name="font">
|
| 863 |
-
<font>
|
| 864 |
-
<family>Arial</family>
|
| 865 |
-
</font>
|
| 866 |
-
</property>
|
| 867 |
-
</action>
|
| 868 |
-
<action name="actionPopulation_Analysis">
|
| 869 |
-
<property name="text">
|
| 870 |
-
<string>Population Analysis</string>
|
| 871 |
-
</property>
|
| 872 |
-
</action>
|
| 873 |
-
<action name="actionComplete_Target_Analysis">
|
| 874 |
-
<property name="text">
|
| 875 |
-
<string>Complete Target Analysis</string>
|
| 876 |
-
</property>
|
| 877 |
-
</action>
|
| 878 |
-
<action name="actOpenNCBI">
|
| 879 |
-
<property name="text">
|
| 880 |
-
<string>National Center for BioTechnology Information</string>
|
| 881 |
-
</property>
|
| 882 |
-
<property name="font">
|
| 883 |
-
<font>
|
| 884 |
-
<family>Arial</family>
|
| 885 |
-
</font>
|
| 886 |
-
</property>
|
| 887 |
-
</action>
|
| 888 |
-
<action name="actionCasper2">
|
| 889 |
-
<property name="text">
|
| 890 |
-
<string>CASPER Online</string>
|
| 891 |
-
</property>
|
| 892 |
-
<property name="font">
|
| 893 |
-
<font>
|
| 894 |
-
<family>Arial</family>
|
| 895 |
-
</font>
|
| 896 |
-
</property>
|
| 897 |
-
</action>
|
| 898 |
-
<action name="actionCo_Targeting">
|
| 899 |
-
<property name="text">
|
| 900 |
-
<string>Co-Targeting</string>
|
| 901 |
-
</property>
|
| 902 |
-
</action>
|
| 903 |
-
<action name="actOpenRepository">
|
| 904 |
-
<property name="text">
|
| 905 |
-
<string>Visit Repository</string>
|
| 906 |
-
</property>
|
| 907 |
-
<property name="font">
|
| 908 |
-
<font>
|
| 909 |
-
<family>Arial</family>
|
| 910 |
-
</font>
|
| 911 |
-
</property>
|
| 912 |
-
</action>
|
| 913 |
-
<action name="actionNew_2">
|
| 914 |
-
<property name="text">
|
| 915 |
-
<string>New</string>
|
| 916 |
-
</property>
|
| 917 |
-
</action>
|
| 918 |
-
<action name="actiontesting">
|
| 919 |
-
<property name="text">
|
| 920 |
-
<string>testing</string>
|
| 921 |
-
</property>
|
| 922 |
-
</action>
|
| 923 |
-
<action name="actionAbout_CASPER">
|
| 924 |
-
<property name="text">
|
| 925 |
-
<string>About CASPER</string>
|
| 926 |
-
</property>
|
| 927 |
-
</action>
|
| 928 |
-
<action name="actionTest">
|
| 929 |
-
<property name="text">
|
| 930 |
-
<string>Test</string>
|
| 931 |
-
</property>
|
| 932 |
-
</action>
|
| 933 |
-
</widget>
|
| 934 |
-
<tabstops>
|
| 935 |
-
<tabstop>cmbOrganism</tabstop>
|
| 936 |
-
<tabstop>cmbEndonuclease</tabstop>
|
| 937 |
-
<tabstop>pbtnNCBIFileSearch</tabstop>
|
| 938 |
-
<tabstop>cmbLocalAnnotationFiles</tabstop>
|
| 939 |
-
<tabstop>rbtnFeature</tabstop>
|
| 940 |
-
<tabstop>rbtnPosition</tabstop>
|
| 941 |
-
<tabstop>txtedGeneEntry</tabstop>
|
| 942 |
-
<tabstop>pbtnFindTargets</tabstop>
|
| 943 |
-
</tabstops>
|
| 944 |
-
<resources/>
|
| 945 |
-
<connections/>
|
| 946 |
-
</ui>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,149 +0,0 @@
|
|
| 1 |
-
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
-
<ui version="4.0">
|
| 3 |
-
<class>MainWindow</class>
|
| 4 |
-
<widget class="QMainWindow" name="MainWindow">
|
| 5 |
-
<property name="geometry">
|
| 6 |
-
<rect>
|
| 7 |
-
<x>0</x>
|
| 8 |
-
<y>0</y>
|
| 9 |
-
<width>375</width>
|
| 10 |
-
<height>140</height>
|
| 11 |
-
</rect>
|
| 12 |
-
</property>
|
| 13 |
-
<property name="font">
|
| 14 |
-
<font>
|
| 15 |
-
<pointsize>12</pointsize>
|
| 16 |
-
</font>
|
| 17 |
-
</property>
|
| 18 |
-
<property name="windowTitle">
|
| 19 |
-
<string>MainWindow</string>
|
| 20 |
-
</property>
|
| 21 |
-
<widget class="QWidget" name="centralwidget">
|
| 22 |
-
<layout class="QGridLayout" name="gridLayout_2">
|
| 23 |
-
<item row="1" column="1">
|
| 24 |
-
<layout class="QGridLayout" name="gridLayout">
|
| 25 |
-
<item row="2" column="0">
|
| 26 |
-
<widget class="QLabel" name="label">
|
| 27 |
-
<property name="sizePolicy">
|
| 28 |
-
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
| 29 |
-
<horstretch>0</horstretch>
|
| 30 |
-
<verstretch>0</verstretch>
|
| 31 |
-
</sizepolicy>
|
| 32 |
-
</property>
|
| 33 |
-
<property name="toolTip">
|
| 34 |
-
<string><html><head/><body><p>Max table row count represents the max number of repeated seeds to be analyzed and loaded into the table in multitargeting. The <span style=" font-style:italic;">global analysis</span> section is not affected by this value. After changing the value, reload the anlysis but clicking the <span style=" font-weight:600;">Analyze</span> button.</p></body></html></string>
|
| 35 |
-
</property>
|
| 36 |
-
<property name="text">
|
| 37 |
-
<string>Max Table Row Count:</string>
|
| 38 |
-
</property>
|
| 39 |
-
</widget>
|
| 40 |
-
</item>
|
| 41 |
-
<item row="2" column="1">
|
| 42 |
-
<widget class="QLineEdit" name="row_count">
|
| 43 |
-
<property name="sizePolicy">
|
| 44 |
-
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 45 |
-
<horstretch>0</horstretch>
|
| 46 |
-
<verstretch>0</verstretch>
|
| 47 |
-
</sizepolicy>
|
| 48 |
-
</property>
|
| 49 |
-
<property name="toolTip">
|
| 50 |
-
<string><html><head/><body><p>Default value is 1000. Change to -1 for no limit.</p></body></html></string>
|
| 51 |
-
</property>
|
| 52 |
-
<property name="text">
|
| 53 |
-
<string>1000</string>
|
| 54 |
-
</property>
|
| 55 |
-
</widget>
|
| 56 |
-
</item>
|
| 57 |
-
<item row="1" column="0" colspan="2">
|
| 58 |
-
<widget class="Line" name="line">
|
| 59 |
-
<property name="orientation">
|
| 60 |
-
<enum>Qt::Horizontal</enum>
|
| 61 |
-
</property>
|
| 62 |
-
</widget>
|
| 63 |
-
</item>
|
| 64 |
-
<item row="0" column="0" colspan="2">
|
| 65 |
-
<widget class="QLabel" name="title">
|
| 66 |
-
<property name="sizePolicy">
|
| 67 |
-
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
| 68 |
-
<horstretch>0</horstretch>
|
| 69 |
-
<verstretch>0</verstretch>
|
| 70 |
-
</sizepolicy>
|
| 71 |
-
</property>
|
| 72 |
-
<property name="text">
|
| 73 |
-
<string>SQL Settings</string>
|
| 74 |
-
</property>
|
| 75 |
-
</widget>
|
| 76 |
-
</item>
|
| 77 |
-
</layout>
|
| 78 |
-
</item>
|
| 79 |
-
<item row="1" column="2">
|
| 80 |
-
<spacer name="horizontalSpacer_2">
|
| 81 |
-
<property name="orientation">
|
| 82 |
-
<enum>Qt::Horizontal</enum>
|
| 83 |
-
</property>
|
| 84 |
-
<property name="sizeType">
|
| 85 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 86 |
-
</property>
|
| 87 |
-
<property name="sizeHint" stdset="0">
|
| 88 |
-
<size>
|
| 89 |
-
<width>20</width>
|
| 90 |
-
<height>20</height>
|
| 91 |
-
</size>
|
| 92 |
-
</property>
|
| 93 |
-
</spacer>
|
| 94 |
-
</item>
|
| 95 |
-
<item row="1" column="0">
|
| 96 |
-
<spacer name="horizontalSpacer">
|
| 97 |
-
<property name="orientation">
|
| 98 |
-
<enum>Qt::Horizontal</enum>
|
| 99 |
-
</property>
|
| 100 |
-
<property name="sizeType">
|
| 101 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 102 |
-
</property>
|
| 103 |
-
<property name="sizeHint" stdset="0">
|
| 104 |
-
<size>
|
| 105 |
-
<width>20</width>
|
| 106 |
-
<height>20</height>
|
| 107 |
-
</size>
|
| 108 |
-
</property>
|
| 109 |
-
</spacer>
|
| 110 |
-
</item>
|
| 111 |
-
<item row="2" column="1">
|
| 112 |
-
<spacer name="verticalSpacer">
|
| 113 |
-
<property name="orientation">
|
| 114 |
-
<enum>Qt::Vertical</enum>
|
| 115 |
-
</property>
|
| 116 |
-
<property name="sizeType">
|
| 117 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 118 |
-
</property>
|
| 119 |
-
<property name="sizeHint" stdset="0">
|
| 120 |
-
<size>
|
| 121 |
-
<width>20</width>
|
| 122 |
-
<height>20</height>
|
| 123 |
-
</size>
|
| 124 |
-
</property>
|
| 125 |
-
</spacer>
|
| 126 |
-
</item>
|
| 127 |
-
<item row="0" column="1">
|
| 128 |
-
<spacer name="verticalSpacer_2">
|
| 129 |
-
<property name="orientation">
|
| 130 |
-
<enum>Qt::Vertical</enum>
|
| 131 |
-
</property>
|
| 132 |
-
<property name="sizeType">
|
| 133 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 134 |
-
</property>
|
| 135 |
-
<property name="sizeHint" stdset="0">
|
| 136 |
-
<size>
|
| 137 |
-
<width>20</width>
|
| 138 |
-
<height>20</height>
|
| 139 |
-
</size>
|
| 140 |
-
</property>
|
| 141 |
-
</spacer>
|
| 142 |
-
</item>
|
| 143 |
-
</layout>
|
| 144 |
-
</widget>
|
| 145 |
-
<widget class="QStatusBar" name="statusbar"/>
|
| 146 |
-
</widget>
|
| 147 |
-
<resources/>
|
| 148 |
-
<connections/>
|
| 149 |
-
</ui>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,227 +0,0 @@
|
|
| 1 |
-
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
-
<ui version="4.0">
|
| 3 |
-
<class>MainWindow</class>
|
| 4 |
-
<widget class="QMainWindow" name="MainWindow">
|
| 5 |
-
<property name="geometry">
|
| 6 |
-
<rect>
|
| 7 |
-
<x>0</x>
|
| 8 |
-
<y>0</y>
|
| 9 |
-
<width>350</width>
|
| 10 |
-
<height>250</height>
|
| 11 |
-
</rect>
|
| 12 |
-
</property>
|
| 13 |
-
<property name="font">
|
| 14 |
-
<font>
|
| 15 |
-
<pointsize>12</pointsize>
|
| 16 |
-
</font>
|
| 17 |
-
</property>
|
| 18 |
-
<property name="windowTitle">
|
| 19 |
-
<string>MainWindow</string>
|
| 20 |
-
</property>
|
| 21 |
-
<widget class="QWidget" name="centralwidget">
|
| 22 |
-
<layout class="QGridLayout" name="gridLayout_2">
|
| 23 |
-
<item row="2" column="1">
|
| 24 |
-
<spacer name="verticalSpacer">
|
| 25 |
-
<property name="orientation">
|
| 26 |
-
<enum>Qt::Vertical</enum>
|
| 27 |
-
</property>
|
| 28 |
-
<property name="sizeType">
|
| 29 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 30 |
-
</property>
|
| 31 |
-
<property name="sizeHint" stdset="0">
|
| 32 |
-
<size>
|
| 33 |
-
<width>20</width>
|
| 34 |
-
<height>20</height>
|
| 35 |
-
</size>
|
| 36 |
-
</property>
|
| 37 |
-
</spacer>
|
| 38 |
-
</item>
|
| 39 |
-
<item row="1" column="2">
|
| 40 |
-
<spacer name="horizontalSpacer_2">
|
| 41 |
-
<property name="orientation">
|
| 42 |
-
<enum>Qt::Horizontal</enum>
|
| 43 |
-
</property>
|
| 44 |
-
<property name="sizeType">
|
| 45 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 46 |
-
</property>
|
| 47 |
-
<property name="sizeHint" stdset="0">
|
| 48 |
-
<size>
|
| 49 |
-
<width>20</width>
|
| 50 |
-
<height>20</height>
|
| 51 |
-
</size>
|
| 52 |
-
</property>
|
| 53 |
-
</spacer>
|
| 54 |
-
</item>
|
| 55 |
-
<item row="1" column="0">
|
| 56 |
-
<spacer name="horizontalSpacer">
|
| 57 |
-
<property name="orientation">
|
| 58 |
-
<enum>Qt::Horizontal</enum>
|
| 59 |
-
</property>
|
| 60 |
-
<property name="sizeType">
|
| 61 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 62 |
-
</property>
|
| 63 |
-
<property name="sizeHint" stdset="0">
|
| 64 |
-
<size>
|
| 65 |
-
<width>20</width>
|
| 66 |
-
<height>20</height>
|
| 67 |
-
</size>
|
| 68 |
-
</property>
|
| 69 |
-
</spacer>
|
| 70 |
-
</item>
|
| 71 |
-
<item row="0" column="1">
|
| 72 |
-
<spacer name="verticalSpacer_2">
|
| 73 |
-
<property name="orientation">
|
| 74 |
-
<enum>Qt::Vertical</enum>
|
| 75 |
-
</property>
|
| 76 |
-
<property name="sizeType">
|
| 77 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 78 |
-
</property>
|
| 79 |
-
<property name="sizeHint" stdset="0">
|
| 80 |
-
<size>
|
| 81 |
-
<width>20</width>
|
| 82 |
-
<height>20</height>
|
| 83 |
-
</size>
|
| 84 |
-
</property>
|
| 85 |
-
</spacer>
|
| 86 |
-
</item>
|
| 87 |
-
<item row="1" column="1">
|
| 88 |
-
<layout class="QGridLayout" name="gridLayout">
|
| 89 |
-
<item row="2" column="0">
|
| 90 |
-
<widget class="QLabel" name="label_2">
|
| 91 |
-
<property name="sizePolicy">
|
| 92 |
-
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 93 |
-
<horstretch>0</horstretch>
|
| 94 |
-
<verstretch>0</verstretch>
|
| 95 |
-
</sizepolicy>
|
| 96 |
-
</property>
|
| 97 |
-
<property name="text">
|
| 98 |
-
<string>Total Number of Repeats:</string>
|
| 99 |
-
</property>
|
| 100 |
-
</widget>
|
| 101 |
-
</item>
|
| 102 |
-
<item row="4" column="0">
|
| 103 |
-
<widget class="QLabel" name="label_4">
|
| 104 |
-
<property name="sizePolicy">
|
| 105 |
-
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 106 |
-
<horstretch>0</horstretch>
|
| 107 |
-
<verstretch>0</verstretch>
|
| 108 |
-
</sizepolicy>
|
| 109 |
-
</property>
|
| 110 |
-
<property name="text">
|
| 111 |
-
<string>Median Number of Repeats:</string>
|
| 112 |
-
</property>
|
| 113 |
-
</widget>
|
| 114 |
-
</item>
|
| 115 |
-
<item row="5" column="0">
|
| 116 |
-
<widget class="QLabel" name="label_5">
|
| 117 |
-
<property name="sizePolicy">
|
| 118 |
-
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 119 |
-
<horstretch>0</horstretch>
|
| 120 |
-
<verstretch>0</verstretch>
|
| 121 |
-
</sizepolicy>
|
| 122 |
-
</property>
|
| 123 |
-
<property name="text">
|
| 124 |
-
<string>Mode Number of Repeats:</string>
|
| 125 |
-
</property>
|
| 126 |
-
</widget>
|
| 127 |
-
</item>
|
| 128 |
-
<item row="3" column="0">
|
| 129 |
-
<widget class="QLabel" name="label_3">
|
| 130 |
-
<property name="sizePolicy">
|
| 131 |
-
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 132 |
-
<horstretch>0</horstretch>
|
| 133 |
-
<verstretch>0</verstretch>
|
| 134 |
-
</sizepolicy>
|
| 135 |
-
</property>
|
| 136 |
-
<property name="text">
|
| 137 |
-
<string>Average Number of Repeats:</string>
|
| 138 |
-
</property>
|
| 139 |
-
</widget>
|
| 140 |
-
</item>
|
| 141 |
-
<item row="2" column="1">
|
| 142 |
-
<widget class="QLabel" name="nbr_seq">
|
| 143 |
-
<property name="sizePolicy">
|
| 144 |
-
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 145 |
-
<horstretch>0</horstretch>
|
| 146 |
-
<verstretch>0</verstretch>
|
| 147 |
-
</sizepolicy>
|
| 148 |
-
</property>
|
| 149 |
-
<property name="text">
|
| 150 |
-
<string/>
|
| 151 |
-
</property>
|
| 152 |
-
</widget>
|
| 153 |
-
</item>
|
| 154 |
-
<item row="3" column="1">
|
| 155 |
-
<widget class="QLabel" name="avg_rep">
|
| 156 |
-
<property name="sizePolicy">
|
| 157 |
-
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 158 |
-
<horstretch>0</horstretch>
|
| 159 |
-
<verstretch>0</verstretch>
|
| 160 |
-
</sizepolicy>
|
| 161 |
-
</property>
|
| 162 |
-
<property name="text">
|
| 163 |
-
<string/>
|
| 164 |
-
</property>
|
| 165 |
-
</widget>
|
| 166 |
-
</item>
|
| 167 |
-
<item row="4" column="1">
|
| 168 |
-
<widget class="QLabel" name="med_rep">
|
| 169 |
-
<property name="sizePolicy">
|
| 170 |
-
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 171 |
-
<horstretch>0</horstretch>
|
| 172 |
-
<verstretch>0</verstretch>
|
| 173 |
-
</sizepolicy>
|
| 174 |
-
</property>
|
| 175 |
-
<property name="text">
|
| 176 |
-
<string/>
|
| 177 |
-
</property>
|
| 178 |
-
</widget>
|
| 179 |
-
</item>
|
| 180 |
-
<item row="5" column="1">
|
| 181 |
-
<widget class="QLabel" name="mode_rep">
|
| 182 |
-
<property name="sizePolicy">
|
| 183 |
-
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 184 |
-
<horstretch>0</horstretch>
|
| 185 |
-
<verstretch>0</verstretch>
|
| 186 |
-
</sizepolicy>
|
| 187 |
-
</property>
|
| 188 |
-
<property name="text">
|
| 189 |
-
<string/>
|
| 190 |
-
</property>
|
| 191 |
-
</widget>
|
| 192 |
-
</item>
|
| 193 |
-
<item row="0" column="0" colspan="2">
|
| 194 |
-
<widget class="QLabel" name="title">
|
| 195 |
-
<property name="sizePolicy">
|
| 196 |
-
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
| 197 |
-
<horstretch>0</horstretch>
|
| 198 |
-
<verstretch>0</verstretch>
|
| 199 |
-
</sizepolicy>
|
| 200 |
-
</property>
|
| 201 |
-
<property name="text">
|
| 202 |
-
<string>Multitargeting Statistics</string>
|
| 203 |
-
</property>
|
| 204 |
-
</widget>
|
| 205 |
-
</item>
|
| 206 |
-
<item row="1" column="0" colspan="2">
|
| 207 |
-
<widget class="Line" name="line">
|
| 208 |
-
<property name="sizePolicy">
|
| 209 |
-
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 210 |
-
<horstretch>0</horstretch>
|
| 211 |
-
<verstretch>0</verstretch>
|
| 212 |
-
</sizepolicy>
|
| 213 |
-
</property>
|
| 214 |
-
<property name="orientation">
|
| 215 |
-
<enum>Qt::Horizontal</enum>
|
| 216 |
-
</property>
|
| 217 |
-
</widget>
|
| 218 |
-
</item>
|
| 219 |
-
</layout>
|
| 220 |
-
</item>
|
| 221 |
-
</layout>
|
| 222 |
-
</widget>
|
| 223 |
-
<widget class="QStatusBar" name="statusbar"/>
|
| 224 |
-
</widget>
|
| 225 |
-
<resources/>
|
| 226 |
-
<connections/>
|
| 227 |
-
</ui>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -42,23 +42,37 @@
|
|
| 42 |
<string>Select Organism and Endonuclease:</string>
|
| 43 |
</property>
|
| 44 |
<layout class="QGridLayout" name="gridLayout_8">
|
| 45 |
-
<item row="
|
| 46 |
-
<widget class="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
<property name="sizePolicy">
|
| 48 |
-
<sizepolicy hsizetype="
|
| 49 |
<horstretch>0</horstretch>
|
| 50 |
<verstretch>0</verstretch>
|
| 51 |
</sizepolicy>
|
| 52 |
</property>
|
| 53 |
-
<property name="
|
| 54 |
-
<
|
| 55 |
-
<width>0</width>
|
| 56 |
-
<height>0</height>
|
| 57 |
-
</size>
|
| 58 |
</property>
|
| 59 |
</widget>
|
| 60 |
</item>
|
| 61 |
-
<item row="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
<layout class="QHBoxLayout" name="horizontalLayout">
|
| 63 |
<item>
|
| 64 |
<widget class="QPushButton" name="pbtnAnalyze">
|
|
@@ -79,46 +93,42 @@
|
|
| 79 |
</property>
|
| 80 |
</widget>
|
| 81 |
</item>
|
| 82 |
-
<item>
|
| 83 |
-
<widget class="QToolButton" name="tbtnSQLSettings">
|
| 84 |
-
<property name="minimumSize">
|
| 85 |
-
<size>
|
| 86 |
-
<width>0</width>
|
| 87 |
-
<height>0</height>
|
| 88 |
-
</size>
|
| 89 |
-
</property>
|
| 90 |
-
<property name="text">
|
| 91 |
-
<string>...</string>
|
| 92 |
-
</property>
|
| 93 |
-
</widget>
|
| 94 |
-
</item>
|
| 95 |
</layout>
|
| 96 |
</item>
|
| 97 |
<item row="0" column="1">
|
| 98 |
-
<widget class="
|
| 99 |
<property name="sizePolicy">
|
| 100 |
-
<sizepolicy hsizetype="
|
| 101 |
<horstretch>0</horstretch>
|
| 102 |
<verstretch>0</verstretch>
|
| 103 |
</sizepolicy>
|
| 104 |
</property>
|
| 105 |
-
<property name="
|
| 106 |
-
<
|
|
|
|
|
|
|
|
|
|
| 107 |
</property>
|
| 108 |
</widget>
|
| 109 |
</item>
|
| 110 |
-
<item row="
|
| 111 |
-
<widget class="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
<property name="minimumSize">
|
| 113 |
<size>
|
| 114 |
-
<width>
|
| 115 |
<height>0</height>
|
| 116 |
</size>
|
| 117 |
</property>
|
| 118 |
</widget>
|
| 119 |
</item>
|
| 120 |
-
<item row="
|
| 121 |
-
<widget class="QLabel" name="
|
| 122 |
<property name="sizePolicy">
|
| 123 |
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
| 124 |
<horstretch>0</horstretch>
|
|
@@ -126,30 +136,39 @@
|
|
| 126 |
</sizepolicy>
|
| 127 |
</property>
|
| 128 |
<property name="text">
|
| 129 |
-
<string>
|
| 130 |
</property>
|
| 131 |
</widget>
|
| 132 |
</item>
|
| 133 |
-
<item row="
|
| 134 |
-
<widget class="
|
| 135 |
<property name="sizePolicy">
|
| 136 |
-
<sizepolicy hsizetype="
|
| 137 |
<horstretch>0</horstretch>
|
| 138 |
<verstretch>0</verstretch>
|
| 139 |
</sizepolicy>
|
| 140 |
</property>
|
| 141 |
-
<property name="
|
| 142 |
-
<
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
</
|
| 146 |
</property>
|
| 147 |
</widget>
|
| 148 |
</item>
|
| 149 |
-
<item row="
|
| 150 |
-
<widget class="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
<property name="text">
|
| 152 |
-
<string>
|
| 153 |
</property>
|
| 154 |
</widget>
|
| 155 |
</item>
|
|
@@ -228,7 +247,7 @@
|
|
| 228 |
<rect>
|
| 229 |
<x>0</x>
|
| 230 |
<y>0</y>
|
| 231 |
-
<width>
|
| 232 |
<height>112</height>
|
| 233 |
</rect>
|
| 234 |
</property>
|
|
@@ -277,25 +296,6 @@
|
|
| 277 |
</property>
|
| 278 |
<layout class="QGridLayout" name="gridLayout_7">
|
| 279 |
<item row="0" column="0">
|
| 280 |
-
<widget class="QPushButton" name="pbtnStatisticsOverview">
|
| 281 |
-
<property name="sizePolicy">
|
| 282 |
-
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 283 |
-
<horstretch>0</horstretch>
|
| 284 |
-
<verstretch>0</verstretch>
|
| 285 |
-
</sizepolicy>
|
| 286 |
-
</property>
|
| 287 |
-
<property name="minimumSize">
|
| 288 |
-
<size>
|
| 289 |
-
<width>0</width>
|
| 290 |
-
<height>0</height>
|
| 291 |
-
</size>
|
| 292 |
-
</property>
|
| 293 |
-
<property name="text">
|
| 294 |
-
<string>Statistics Overview</string>
|
| 295 |
-
</property>
|
| 296 |
-
</widget>
|
| 297 |
-
</item>
|
| 298 |
-
<item row="1" column="0">
|
| 299 |
<widget class="QTabWidget" name="tabsGlobalAnalysis">
|
| 300 |
<property name="sizePolicy">
|
| 301 |
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
|
@@ -304,8 +304,129 @@
|
|
| 304 |
</sizepolicy>
|
| 305 |
</property>
|
| 306 |
<property name="currentIndex">
|
| 307 |
-
<number>
|
| 308 |
</property>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 309 |
<widget class="QWidget" name="tabRepeatsVsSeed">
|
| 310 |
<attribute name="title">
|
| 311 |
<string>Repeats per Seed ID Number</string>
|
|
@@ -345,27 +466,8 @@
|
|
| 345 |
</property>
|
| 346 |
</widget>
|
| 347 |
</item>
|
| 348 |
-
<item row="3" column="0" alignment="Qt::AlignLeft">
|
| 349 |
-
<widget class="QPushButton" name="pbtnBank">
|
| 350 |
-
<property name="sizePolicy">
|
| 351 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 352 |
-
<horstretch>0</horstretch>
|
| 353 |
-
<verstretch>0</verstretch>
|
| 354 |
-
</sizepolicy>
|
| 355 |
-
</property>
|
| 356 |
-
<property name="minimumSize">
|
| 357 |
-
<size>
|
| 358 |
-
<width>125</width>
|
| 359 |
-
<height>0</height>
|
| 360 |
-
</size>
|
| 361 |
-
</property>
|
| 362 |
-
<property name="text">
|
| 363 |
-
<string>Back</string>
|
| 364 |
-
</property>
|
| 365 |
-
</widget>
|
| 366 |
-
</item>
|
| 367 |
<item row="3" column="1" alignment="Qt::AlignRight">
|
| 368 |
-
<widget class="QPushButton" name="
|
| 369 |
<property name="sizePolicy">
|
| 370 |
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 371 |
<horstretch>0</horstretch>
|
|
|
|
| 42 |
<string>Select Organism and Endonuclease:</string>
|
| 43 |
</property>
|
| 44 |
<layout class="QGridLayout" name="gridLayout_8">
|
| 45 |
+
<item row="8" column="0" colspan="3">
|
| 46 |
+
<widget class="QTableWidget" name="tblSeeds">
|
| 47 |
+
<property name="minimumSize">
|
| 48 |
+
<size>
|
| 49 |
+
<width>400</width>
|
| 50 |
+
<height>0</height>
|
| 51 |
+
</size>
|
| 52 |
+
</property>
|
| 53 |
+
</widget>
|
| 54 |
+
</item>
|
| 55 |
+
<item row="0" column="0">
|
| 56 |
+
<widget class="QLabel" name="lblOrganism">
|
| 57 |
<property name="sizePolicy">
|
| 58 |
+
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
| 59 |
<horstretch>0</horstretch>
|
| 60 |
<verstretch>0</verstretch>
|
| 61 |
</sizepolicy>
|
| 62 |
</property>
|
| 63 |
+
<property name="text">
|
| 64 |
+
<string>Organism:</string>
|
|
|
|
|
|
|
|
|
|
| 65 |
</property>
|
| 66 |
</widget>
|
| 67 |
</item>
|
| 68 |
+
<item row="7" column="0">
|
| 69 |
+
<widget class="QCheckBox" name="chkSelectAll">
|
| 70 |
+
<property name="text">
|
| 71 |
+
<string>Select All</string>
|
| 72 |
+
</property>
|
| 73 |
+
</widget>
|
| 74 |
+
</item>
|
| 75 |
+
<item row="6" column="0" colspan="3">
|
| 76 |
<layout class="QHBoxLayout" name="horizontalLayout">
|
| 77 |
<item>
|
| 78 |
<widget class="QPushButton" name="pbtnAnalyze">
|
|
|
|
| 93 |
</property>
|
| 94 |
</widget>
|
| 95 |
</item>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
</layout>
|
| 97 |
</item>
|
| 98 |
<item row="0" column="1">
|
| 99 |
+
<widget class="QComboBox" name="cmbOrganism">
|
| 100 |
<property name="sizePolicy">
|
| 101 |
+
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 102 |
<horstretch>0</horstretch>
|
| 103 |
<verstretch>0</verstretch>
|
| 104 |
</sizepolicy>
|
| 105 |
</property>
|
| 106 |
+
<property name="minimumSize">
|
| 107 |
+
<size>
|
| 108 |
+
<width>0</width>
|
| 109 |
+
<height>0</height>
|
| 110 |
+
</size>
|
| 111 |
</property>
|
| 112 |
</widget>
|
| 113 |
</item>
|
| 114 |
+
<item row="1" column="1">
|
| 115 |
+
<widget class="QComboBox" name="cmbEndonuclease">
|
| 116 |
+
<property name="sizePolicy">
|
| 117 |
+
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 118 |
+
<horstretch>0</horstretch>
|
| 119 |
+
<verstretch>0</verstretch>
|
| 120 |
+
</sizepolicy>
|
| 121 |
+
</property>
|
| 122 |
<property name="minimumSize">
|
| 123 |
<size>
|
| 124 |
+
<width>0</width>
|
| 125 |
<height>0</height>
|
| 126 |
</size>
|
| 127 |
</property>
|
| 128 |
</widget>
|
| 129 |
</item>
|
| 130 |
+
<item row="1" column="0">
|
| 131 |
+
<widget class="QLabel" name="lblEndonuclease">
|
| 132 |
<property name="sizePolicy">
|
| 133 |
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
| 134 |
<horstretch>0</horstretch>
|
|
|
|
| 136 |
</sizepolicy>
|
| 137 |
</property>
|
| 138 |
<property name="text">
|
| 139 |
+
<string>Endonuclease:</string>
|
| 140 |
</property>
|
| 141 |
</widget>
|
| 142 |
</item>
|
| 143 |
+
<item row="2" column="1">
|
| 144 |
+
<widget class="QLineEdit" name="ledMaxResults">
|
| 145 |
<property name="sizePolicy">
|
| 146 |
+
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 147 |
<horstretch>0</horstretch>
|
| 148 |
<verstretch>0</verstretch>
|
| 149 |
</sizepolicy>
|
| 150 |
</property>
|
| 151 |
+
<property name="toolTip">
|
| 152 |
+
<string><html><head/><body><p>Default value is 1000. Change to -1 for no limit.</p></body></html></string>
|
| 153 |
+
</property>
|
| 154 |
+
<property name="text">
|
| 155 |
+
<string>1000</string>
|
| 156 |
</property>
|
| 157 |
</widget>
|
| 158 |
</item>
|
| 159 |
+
<item row="2" column="0">
|
| 160 |
+
<widget class="QLabel" name="lblMaxResults">
|
| 161 |
+
<property name="sizePolicy">
|
| 162 |
+
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
| 163 |
+
<horstretch>0</horstretch>
|
| 164 |
+
<verstretch>0</verstretch>
|
| 165 |
+
</sizepolicy>
|
| 166 |
+
</property>
|
| 167 |
+
<property name="toolTip">
|
| 168 |
+
<string><html><head/><body><p>Max table row count represents the max number of repeated seeds to be analyzed and loaded into the table in multitargeting. The <span style=" font-style:italic;">global analysis</span> section is not affected by this value. After changing the value, reload the anlysis but clicking the <span style=" font-weight:600;">Analyze</span> button.</p></body></html></string>
|
| 169 |
+
</property>
|
| 170 |
<property name="text">
|
| 171 |
+
<string>Max Results:</string>
|
| 172 |
</property>
|
| 173 |
</widget>
|
| 174 |
</item>
|
|
|
|
| 247 |
<rect>
|
| 248 |
<x>0</x>
|
| 249 |
<y>0</y>
|
| 250 |
+
<width>374</width>
|
| 251 |
<height>112</height>
|
| 252 |
</rect>
|
| 253 |
</property>
|
|
|
|
| 296 |
</property>
|
| 297 |
<layout class="QGridLayout" name="gridLayout_7">
|
| 298 |
<item row="0" column="0">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 299 |
<widget class="QTabWidget" name="tabsGlobalAnalysis">
|
| 300 |
<property name="sizePolicy">
|
| 301 |
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
|
|
|
| 304 |
</sizepolicy>
|
| 305 |
</property>
|
| 306 |
<property name="currentIndex">
|
| 307 |
+
<number>0</number>
|
| 308 |
</property>
|
| 309 |
+
<widget class="QWidget" name="tabStatisticsOverview">
|
| 310 |
+
<attribute name="title">
|
| 311 |
+
<string>Statistics Overview</string>
|
| 312 |
+
</attribute>
|
| 313 |
+
<widget class="QWidget" name="layoutWidget">
|
| 314 |
+
<property name="geometry">
|
| 315 |
+
<rect>
|
| 316 |
+
<x>10</x>
|
| 317 |
+
<y>10</y>
|
| 318 |
+
<width>371</width>
|
| 319 |
+
<height>133</height>
|
| 320 |
+
</rect>
|
| 321 |
+
</property>
|
| 322 |
+
<layout class="QGridLayout" name="gridLayout_3">
|
| 323 |
+
<item row="2" column="1">
|
| 324 |
+
<widget class="QLabel" name="lblMedianRepeatsValue">
|
| 325 |
+
<property name="sizePolicy">
|
| 326 |
+
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 327 |
+
<horstretch>0</horstretch>
|
| 328 |
+
<verstretch>0</verstretch>
|
| 329 |
+
</sizepolicy>
|
| 330 |
+
</property>
|
| 331 |
+
<property name="text">
|
| 332 |
+
<string/>
|
| 333 |
+
</property>
|
| 334 |
+
</widget>
|
| 335 |
+
</item>
|
| 336 |
+
<item row="0" column="1">
|
| 337 |
+
<widget class="QLabel" name="lblTotalRepeatsValue">
|
| 338 |
+
<property name="sizePolicy">
|
| 339 |
+
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 340 |
+
<horstretch>0</horstretch>
|
| 341 |
+
<verstretch>0</verstretch>
|
| 342 |
+
</sizepolicy>
|
| 343 |
+
</property>
|
| 344 |
+
<property name="text">
|
| 345 |
+
<string/>
|
| 346 |
+
</property>
|
| 347 |
+
</widget>
|
| 348 |
+
</item>
|
| 349 |
+
<item row="2" column="0">
|
| 350 |
+
<widget class="QLabel" name="lblMedianRepeats">
|
| 351 |
+
<property name="sizePolicy">
|
| 352 |
+
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 353 |
+
<horstretch>0</horstretch>
|
| 354 |
+
<verstretch>0</verstretch>
|
| 355 |
+
</sizepolicy>
|
| 356 |
+
</property>
|
| 357 |
+
<property name="text">
|
| 358 |
+
<string>Median Number of Repeats:</string>
|
| 359 |
+
</property>
|
| 360 |
+
</widget>
|
| 361 |
+
</item>
|
| 362 |
+
<item row="1" column="0">
|
| 363 |
+
<widget class="QLabel" name="lblAverageRepeats">
|
| 364 |
+
<property name="sizePolicy">
|
| 365 |
+
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 366 |
+
<horstretch>0</horstretch>
|
| 367 |
+
<verstretch>0</verstretch>
|
| 368 |
+
</sizepolicy>
|
| 369 |
+
</property>
|
| 370 |
+
<property name="text">
|
| 371 |
+
<string>Average Number of Repeats:</string>
|
| 372 |
+
</property>
|
| 373 |
+
</widget>
|
| 374 |
+
</item>
|
| 375 |
+
<item row="3" column="1">
|
| 376 |
+
<widget class="QLabel" name="lblModeRepeatsValue">
|
| 377 |
+
<property name="sizePolicy">
|
| 378 |
+
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 379 |
+
<horstretch>0</horstretch>
|
| 380 |
+
<verstretch>0</verstretch>
|
| 381 |
+
</sizepolicy>
|
| 382 |
+
</property>
|
| 383 |
+
<property name="text">
|
| 384 |
+
<string/>
|
| 385 |
+
</property>
|
| 386 |
+
</widget>
|
| 387 |
+
</item>
|
| 388 |
+
<item row="3" column="0">
|
| 389 |
+
<widget class="QLabel" name="lblModeRepeats">
|
| 390 |
+
<property name="sizePolicy">
|
| 391 |
+
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 392 |
+
<horstretch>0</horstretch>
|
| 393 |
+
<verstretch>0</verstretch>
|
| 394 |
+
</sizepolicy>
|
| 395 |
+
</property>
|
| 396 |
+
<property name="text">
|
| 397 |
+
<string>Mode Number of Repeats:</string>
|
| 398 |
+
</property>
|
| 399 |
+
</widget>
|
| 400 |
+
</item>
|
| 401 |
+
<item row="0" column="0">
|
| 402 |
+
<widget class="QLabel" name="lblTotalRepeats">
|
| 403 |
+
<property name="sizePolicy">
|
| 404 |
+
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 405 |
+
<horstretch>0</horstretch>
|
| 406 |
+
<verstretch>0</verstretch>
|
| 407 |
+
</sizepolicy>
|
| 408 |
+
</property>
|
| 409 |
+
<property name="text">
|
| 410 |
+
<string>Total Number of Repeats:</string>
|
| 411 |
+
</property>
|
| 412 |
+
</widget>
|
| 413 |
+
</item>
|
| 414 |
+
<item row="1" column="1">
|
| 415 |
+
<widget class="QLabel" name="lblAverageRepeatsValue">
|
| 416 |
+
<property name="sizePolicy">
|
| 417 |
+
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 418 |
+
<horstretch>0</horstretch>
|
| 419 |
+
<verstretch>0</verstretch>
|
| 420 |
+
</sizepolicy>
|
| 421 |
+
</property>
|
| 422 |
+
<property name="text">
|
| 423 |
+
<string/>
|
| 424 |
+
</property>
|
| 425 |
+
</widget>
|
| 426 |
+
</item>
|
| 427 |
+
</layout>
|
| 428 |
+
</widget>
|
| 429 |
+
</widget>
|
| 430 |
<widget class="QWidget" name="tabRepeatsVsSeed">
|
| 431 |
<attribute name="title">
|
| 432 |
<string>Repeats per Seed ID Number</string>
|
|
|
|
| 466 |
</property>
|
| 467 |
</widget>
|
| 468 |
</item>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 469 |
<item row="3" column="1" alignment="Qt::AlignRight">
|
| 470 |
+
<widget class="QPushButton" name="pbtnExportSelectedgRNAs">
|
| 471 |
<property name="sizePolicy">
|
| 472 |
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 473 |
<horstretch>0</horstretch>
|
|
File without changes
|
|
@@ -1,335 +0,0 @@
|
|
| 1 |
-
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
-
<ui version="4.0">
|
| 3 |
-
<class>MainWindow</class>
|
| 4 |
-
<widget class="QMainWindow" name="MainWindow">
|
| 5 |
-
<property name="geometry">
|
| 6 |
-
<rect>
|
| 7 |
-
<x>0</x>
|
| 8 |
-
<y>0</y>
|
| 9 |
-
<width>865</width>
|
| 10 |
-
<height>723</height>
|
| 11 |
-
</rect>
|
| 12 |
-
</property>
|
| 13 |
-
<property name="font">
|
| 14 |
-
<font>
|
| 15 |
-
<pointsize>12</pointsize>
|
| 16 |
-
</font>
|
| 17 |
-
</property>
|
| 18 |
-
<property name="windowTitle">
|
| 19 |
-
<string>MainWindow</string>
|
| 20 |
-
</property>
|
| 21 |
-
<widget class="QWidget" name="centralwidget">
|
| 22 |
-
<layout class="QGridLayout" name="gridLayout_2">
|
| 23 |
-
<item row="2" column="0">
|
| 24 |
-
<layout class="QGridLayout" name="gridLayout">
|
| 25 |
-
<item row="2" column="1">
|
| 26 |
-
<widget class="QGroupBox" name="Step3">
|
| 27 |
-
<property name="sizePolicy">
|
| 28 |
-
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
| 29 |
-
<horstretch>0</horstretch>
|
| 30 |
-
<verstretch>0</verstretch>
|
| 31 |
-
</sizepolicy>
|
| 32 |
-
</property>
|
| 33 |
-
<property name="title">
|
| 34 |
-
<string>Step 3: Download Files</string>
|
| 35 |
-
</property>
|
| 36 |
-
<layout class="QGridLayout" name="gridLayout_5">
|
| 37 |
-
<item row="2" column="1" colspan="2">
|
| 38 |
-
<widget class="QLabel" name="progressLabel">
|
| 39 |
-
<property name="text">
|
| 40 |
-
<string/>
|
| 41 |
-
</property>
|
| 42 |
-
</widget>
|
| 43 |
-
</item>
|
| 44 |
-
<item row="3" column="0" colspan="3">
|
| 45 |
-
<widget class="QProgressBar" name="progressBar">
|
| 46 |
-
<property name="value">
|
| 47 |
-
<number>24</number>
|
| 48 |
-
</property>
|
| 49 |
-
</widget>
|
| 50 |
-
</item>
|
| 51 |
-
<item row="0" column="2">
|
| 52 |
-
<widget class="QRadioButton" name="genbank_checkbox">
|
| 53 |
-
<property name="text">
|
| 54 |
-
<string>GenBank</string>
|
| 55 |
-
</property>
|
| 56 |
-
<property name="checked">
|
| 57 |
-
<bool>false</bool>
|
| 58 |
-
</property>
|
| 59 |
-
</widget>
|
| 60 |
-
</item>
|
| 61 |
-
<item row="0" column="0">
|
| 62 |
-
<widget class="QLabel" name="label_6">
|
| 63 |
-
<property name="toolTip">
|
| 64 |
-
<string><html><head/><body><p><span style=" font-size:12pt;">These are the databases that files are downloaded from. Refseq is considered the gold standard for annotations and assemblies. It is HIGHLY recommended to use Refseq if available for the genome of interest.</span></p></body></html></string>
|
| 65 |
-
</property>
|
| 66 |
-
<property name="text">
|
| 67 |
-
<string><html><head/><body><p><span style=" color:#ff0000;">*</span> Collections:</p></body></html></string>
|
| 68 |
-
</property>
|
| 69 |
-
</widget>
|
| 70 |
-
</item>
|
| 71 |
-
<item row="1" column="2">
|
| 72 |
-
<widget class="QCheckBox" name="gbff_checkbox">
|
| 73 |
-
<property name="text">
|
| 74 |
-
<string>GBFF</string>
|
| 75 |
-
</property>
|
| 76 |
-
<property name="checked">
|
| 77 |
-
<bool>true</bool>
|
| 78 |
-
</property>
|
| 79 |
-
</widget>
|
| 80 |
-
</item>
|
| 81 |
-
<item row="1" column="0">
|
| 82 |
-
<widget class="QLabel" name="label_7">
|
| 83 |
-
<property name="toolTip">
|
| 84 |
-
<string><html><head/><body><p><span style=" font-size:12pt;">These are the file types that you are choosing to download. GBFF = annotation file, FASTA/FNA = genomic sequence file.</span></p></body></html></string>
|
| 85 |
-
</property>
|
| 86 |
-
<property name="text">
|
| 87 |
-
<string><html><head/><body><p><span style=" color:#ff0000;">*</span> File Types:</p></body></html></string>
|
| 88 |
-
</property>
|
| 89 |
-
</widget>
|
| 90 |
-
</item>
|
| 91 |
-
<item row="1" column="1">
|
| 92 |
-
<widget class="QCheckBox" name="fna_checkbox">
|
| 93 |
-
<property name="text">
|
| 94 |
-
<string>FASTA/FNA</string>
|
| 95 |
-
</property>
|
| 96 |
-
<property name="checked">
|
| 97 |
-
<bool>true</bool>
|
| 98 |
-
</property>
|
| 99 |
-
</widget>
|
| 100 |
-
</item>
|
| 101 |
-
<item row="2" column="0">
|
| 102 |
-
<widget class="QPushButton" name="download_button">
|
| 103 |
-
<property name="minimumSize">
|
| 104 |
-
<size>
|
| 105 |
-
<width>125</width>
|
| 106 |
-
<height>0</height>
|
| 107 |
-
</size>
|
| 108 |
-
</property>
|
| 109 |
-
<property name="text">
|
| 110 |
-
<string>Download Files</string>
|
| 111 |
-
</property>
|
| 112 |
-
</widget>
|
| 113 |
-
</item>
|
| 114 |
-
<item row="0" column="1">
|
| 115 |
-
<widget class="QRadioButton" name="refseq_checkbox">
|
| 116 |
-
<property name="text">
|
| 117 |
-
<string>RefSeq</string>
|
| 118 |
-
</property>
|
| 119 |
-
<property name="checked">
|
| 120 |
-
<bool>true</bool>
|
| 121 |
-
</property>
|
| 122 |
-
</widget>
|
| 123 |
-
</item>
|
| 124 |
-
<item row="5" column="0" colspan="3">
|
| 125 |
-
<layout class="QFormLayout" name="formLayout">
|
| 126 |
-
<property name="fieldGrowthPolicy">
|
| 127 |
-
<enum>QFormLayout::FieldGrowthPolicy::ExpandingFieldsGrow</enum>
|
| 128 |
-
</property>
|
| 129 |
-
</layout>
|
| 130 |
-
</item>
|
| 131 |
-
</layout>
|
| 132 |
-
</widget>
|
| 133 |
-
</item>
|
| 134 |
-
<item row="1" column="0" colspan="2">
|
| 135 |
-
<widget class="Line" name="line">
|
| 136 |
-
<property name="orientation">
|
| 137 |
-
<enum>Qt::Orientation::Horizontal</enum>
|
| 138 |
-
</property>
|
| 139 |
-
</widget>
|
| 140 |
-
</item>
|
| 141 |
-
<item row="3" column="0" colspan="2">
|
| 142 |
-
<widget class="QGroupBox" name="Step2">
|
| 143 |
-
<property name="title">
|
| 144 |
-
<string>Step 2: Search and Select Files</string>
|
| 145 |
-
</property>
|
| 146 |
-
<layout class="QGridLayout" name="gridLayout_6">
|
| 147 |
-
<item row="0" column="1" alignment="Qt::AlignmentFlag::AlignRight">
|
| 148 |
-
<widget class="QCheckBox" name="all_rows">
|
| 149 |
-
<property name="text">
|
| 150 |
-
<string>Select all rows</string>
|
| 151 |
-
</property>
|
| 152 |
-
</widget>
|
| 153 |
-
</item>
|
| 154 |
-
<item row="0" column="0" alignment="Qt::AlignmentFlag::AlignLeft">
|
| 155 |
-
<widget class="QPushButton" name="search_button">
|
| 156 |
-
<property name="minimumSize">
|
| 157 |
-
<size>
|
| 158 |
-
<width>125</width>
|
| 159 |
-
<height>0</height>
|
| 160 |
-
</size>
|
| 161 |
-
</property>
|
| 162 |
-
<property name="text">
|
| 163 |
-
<string>Search</string>
|
| 164 |
-
</property>
|
| 165 |
-
</widget>
|
| 166 |
-
</item>
|
| 167 |
-
<item row="1" column="0" colspan="2">
|
| 168 |
-
<widget class="QTableView" name="ncbi_table">
|
| 169 |
-
<property name="layoutDirection">
|
| 170 |
-
<enum>Qt::LayoutDirection::LeftToRight</enum>
|
| 171 |
-
</property>
|
| 172 |
-
</widget>
|
| 173 |
-
</item>
|
| 174 |
-
</layout>
|
| 175 |
-
</widget>
|
| 176 |
-
</item>
|
| 177 |
-
<item row="2" column="0">
|
| 178 |
-
<widget class="QGroupBox" name="Step1">
|
| 179 |
-
<property name="sizePolicy">
|
| 180 |
-
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
| 181 |
-
<horstretch>0</horstretch>
|
| 182 |
-
<verstretch>0</verstretch>
|
| 183 |
-
</sizepolicy>
|
| 184 |
-
</property>
|
| 185 |
-
<property name="title">
|
| 186 |
-
<string>Step 1: Input Search Options</string>
|
| 187 |
-
</property>
|
| 188 |
-
<layout class="QGridLayout" name="gridLayout_4">
|
| 189 |
-
<item row="3" column="1">
|
| 190 |
-
<widget class="QCheckBox" name="yes_box">
|
| 191 |
-
<property name="text">
|
| 192 |
-
<string/>
|
| 193 |
-
</property>
|
| 194 |
-
</widget>
|
| 195 |
-
</item>
|
| 196 |
-
<item row="0" column="0">
|
| 197 |
-
<widget class="QLabel" name="label">
|
| 198 |
-
<property name="sizePolicy">
|
| 199 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 200 |
-
<horstretch>0</horstretch>
|
| 201 |
-
<verstretch>0</verstretch>
|
| 202 |
-
</sizepolicy>
|
| 203 |
-
</property>
|
| 204 |
-
<property name="text">
|
| 205 |
-
<string><html><head/><body><p><span style=" color:#ff0000;">* </span>Organism</p></body></html></string>
|
| 206 |
-
</property>
|
| 207 |
-
</widget>
|
| 208 |
-
</item>
|
| 209 |
-
<item row="0" column="1">
|
| 210 |
-
<widget class="QLineEdit" name="organism_line_edit">
|
| 211 |
-
<property name="placeholderText">
|
| 212 |
-
<string>Ex. Escherichia coli</string>
|
| 213 |
-
</property>
|
| 214 |
-
</widget>
|
| 215 |
-
</item>
|
| 216 |
-
<item row="3" column="0">
|
| 217 |
-
<widget class="QLabel" name="label_5">
|
| 218 |
-
<property name="sizePolicy">
|
| 219 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 220 |
-
<horstretch>0</horstretch>
|
| 221 |
-
<verstretch>0</verstretch>
|
| 222 |
-
</sizepolicy>
|
| 223 |
-
</property>
|
| 224 |
-
<property name="toolTip">
|
| 225 |
-
<string><html><head/><body><p><span style=" font-size:12pt;">Only display entries classified as &quot;Complete Genomes&quot; by NCBI.</span></p></body></html></string>
|
| 226 |
-
</property>
|
| 227 |
-
<property name="text">
|
| 228 |
-
<string>Complete Genomes Only</string>
|
| 229 |
-
</property>
|
| 230 |
-
</widget>
|
| 231 |
-
</item>
|
| 232 |
-
<item row="2" column="1">
|
| 233 |
-
<widget class="QLineEdit" name="ret_max_line_edit">
|
| 234 |
-
<property name="text">
|
| 235 |
-
<string>100</string>
|
| 236 |
-
</property>
|
| 237 |
-
</widget>
|
| 238 |
-
</item>
|
| 239 |
-
<item row="1" column="0">
|
| 240 |
-
<widget class="QLabel" name="label_3">
|
| 241 |
-
<property name="sizePolicy">
|
| 242 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 243 |
-
<horstretch>0</horstretch>
|
| 244 |
-
<verstretch>0</verstretch>
|
| 245 |
-
</sizepolicy>
|
| 246 |
-
</property>
|
| 247 |
-
<property name="text">
|
| 248 |
-
<string>Strain Designation</string>
|
| 249 |
-
</property>
|
| 250 |
-
</widget>
|
| 251 |
-
</item>
|
| 252 |
-
<item row="1" column="1">
|
| 253 |
-
<widget class="QLineEdit" name="infra_name_line_edit">
|
| 254 |
-
<property name="placeholderText">
|
| 255 |
-
<string>Ex. K-12</string>
|
| 256 |
-
</property>
|
| 257 |
-
</widget>
|
| 258 |
-
</item>
|
| 259 |
-
<item row="2" column="0">
|
| 260 |
-
<widget class="QLabel" name="label_4">
|
| 261 |
-
<property name="sizePolicy">
|
| 262 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 263 |
-
<horstretch>0</horstretch>
|
| 264 |
-
<verstretch>0</verstretch>
|
| 265 |
-
</sizepolicy>
|
| 266 |
-
</property>
|
| 267 |
-
<property name="toolTip">
|
| 268 |
-
<string><html><head/><body><p><span style=" font-size:12pt;">Number of search results to return.</span></p></body></html></string>
|
| 269 |
-
</property>
|
| 270 |
-
<property name="text">
|
| 271 |
-
<string>Return Max (Default = 100)</string>
|
| 272 |
-
</property>
|
| 273 |
-
</widget>
|
| 274 |
-
</item>
|
| 275 |
-
</layout>
|
| 276 |
-
</widget>
|
| 277 |
-
</item>
|
| 278 |
-
<item row="4" column="0" alignment="Qt::AlignmentFlag::AlignLeft">
|
| 279 |
-
<widget class="QPushButton" name="back_button">
|
| 280 |
-
<property name="minimumSize">
|
| 281 |
-
<size>
|
| 282 |
-
<width>125</width>
|
| 283 |
-
<height>0</height>
|
| 284 |
-
</size>
|
| 285 |
-
</property>
|
| 286 |
-
<property name="text">
|
| 287 |
-
<string>Back</string>
|
| 288 |
-
</property>
|
| 289 |
-
</widget>
|
| 290 |
-
</item>
|
| 291 |
-
<item row="0" column="1">
|
| 292 |
-
<widget class="QLabel" name="label_2">
|
| 293 |
-
<property name="text">
|
| 294 |
-
<string><html><head/><body><p><span style=" color:#ff0000;">* Required</span></p></body></html></string>
|
| 295 |
-
</property>
|
| 296 |
-
<property name="alignment">
|
| 297 |
-
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
|
| 298 |
-
</property>
|
| 299 |
-
</widget>
|
| 300 |
-
</item>
|
| 301 |
-
<item row="0" column="0">
|
| 302 |
-
<widget class="QLabel" name="title">
|
| 303 |
-
<property name="sizePolicy">
|
| 304 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 305 |
-
<horstretch>0</horstretch>
|
| 306 |
-
<verstretch>0</verstretch>
|
| 307 |
-
</sizepolicy>
|
| 308 |
-
</property>
|
| 309 |
-
<property name="text">
|
| 310 |
-
<string>NCBI Tool</string>
|
| 311 |
-
</property>
|
| 312 |
-
</widget>
|
| 313 |
-
</item>
|
| 314 |
-
</layout>
|
| 315 |
-
</item>
|
| 316 |
-
</layout>
|
| 317 |
-
</widget>
|
| 318 |
-
<widget class="QStatusBar" name="statusbar"/>
|
| 319 |
-
</widget>
|
| 320 |
-
<tabstops>
|
| 321 |
-
<tabstop>organism_line_edit</tabstop>
|
| 322 |
-
<tabstop>infra_name_line_edit</tabstop>
|
| 323 |
-
<tabstop>ret_max_line_edit</tabstop>
|
| 324 |
-
<tabstop>yes_box</tabstop>
|
| 325 |
-
<tabstop>search_button</tabstop>
|
| 326 |
-
<tabstop>all_rows</tabstop>
|
| 327 |
-
<tabstop>ncbi_table</tabstop>
|
| 328 |
-
<tabstop>genbank_checkbox</tabstop>
|
| 329 |
-
<tabstop>refseq_checkbox</tabstop>
|
| 330 |
-
<tabstop>download_button</tabstop>
|
| 331 |
-
<tabstop>back_button</tabstop>
|
| 332 |
-
</tabstops>
|
| 333 |
-
<resources/>
|
| 334 |
-
<connections/>
|
| 335 |
-
</ui>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,495 +0,0 @@
|
|
| 1 |
-
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
-
<ui version="4.0">
|
| 3 |
-
<class>MainWindow</class>
|
| 4 |
-
<widget class="QMainWindow" name="MainWindow">
|
| 5 |
-
<property name="geometry">
|
| 6 |
-
<rect>
|
| 7 |
-
<x>0</x>
|
| 8 |
-
<y>0</y>
|
| 9 |
-
<width>865</width>
|
| 10 |
-
<height>723</height>
|
| 11 |
-
</rect>
|
| 12 |
-
</property>
|
| 13 |
-
<property name="font">
|
| 14 |
-
<font>
|
| 15 |
-
<pointsize>12</pointsize>
|
| 16 |
-
</font>
|
| 17 |
-
</property>
|
| 18 |
-
<property name="windowTitle">
|
| 19 |
-
<string>MainWindow</string>
|
| 20 |
-
</property>
|
| 21 |
-
<widget class="QWidget" name="centralwidget">
|
| 22 |
-
<layout class="QGridLayout" name="gridLayout_2">
|
| 23 |
-
<item row="2" column="1">
|
| 24 |
-
<spacer name="verticalSpacer">
|
| 25 |
-
<property name="orientation">
|
| 26 |
-
<enum>Qt::Vertical</enum>
|
| 27 |
-
</property>
|
| 28 |
-
<property name="sizeType">
|
| 29 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 30 |
-
</property>
|
| 31 |
-
<property name="sizeHint" stdset="0">
|
| 32 |
-
<size>
|
| 33 |
-
<width>20</width>
|
| 34 |
-
<height>20</height>
|
| 35 |
-
</size>
|
| 36 |
-
</property>
|
| 37 |
-
</spacer>
|
| 38 |
-
</item>
|
| 39 |
-
<item row="1" column="0">
|
| 40 |
-
<spacer name="horizontalSpacer">
|
| 41 |
-
<property name="orientation">
|
| 42 |
-
<enum>Qt::Horizontal</enum>
|
| 43 |
-
</property>
|
| 44 |
-
<property name="sizeType">
|
| 45 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 46 |
-
</property>
|
| 47 |
-
<property name="sizeHint" stdset="0">
|
| 48 |
-
<size>
|
| 49 |
-
<width>20</width>
|
| 50 |
-
<height>20</height>
|
| 51 |
-
</size>
|
| 52 |
-
</property>
|
| 53 |
-
</spacer>
|
| 54 |
-
</item>
|
| 55 |
-
<item row="1" column="1">
|
| 56 |
-
<layout class="QGridLayout" name="gridLayout">
|
| 57 |
-
<item row="3" column="0" colspan="2">
|
| 58 |
-
<widget class="QGroupBox" name="Step2">
|
| 59 |
-
<property name="title">
|
| 60 |
-
<string>Step 2: Search and Select Files</string>
|
| 61 |
-
</property>
|
| 62 |
-
<layout class="QGridLayout" name="gridLayout_6">
|
| 63 |
-
<item row="1" column="0" alignment="Qt::AlignLeft">
|
| 64 |
-
<widget class="QPushButton" name="search_button">
|
| 65 |
-
<property name="minimumSize">
|
| 66 |
-
<size>
|
| 67 |
-
<width>125</width>
|
| 68 |
-
<height>0</height>
|
| 69 |
-
</size>
|
| 70 |
-
</property>
|
| 71 |
-
<property name="text">
|
| 72 |
-
<string>Search</string>
|
| 73 |
-
</property>
|
| 74 |
-
</widget>
|
| 75 |
-
</item>
|
| 76 |
-
<item row="3" column="0" colspan="3">
|
| 77 |
-
<spacer name="verticalSpacer_6">
|
| 78 |
-
<property name="orientation">
|
| 79 |
-
<enum>Qt::Vertical</enum>
|
| 80 |
-
</property>
|
| 81 |
-
<property name="sizeType">
|
| 82 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 83 |
-
</property>
|
| 84 |
-
<property name="sizeHint" stdset="0">
|
| 85 |
-
<size>
|
| 86 |
-
<width>10</width>
|
| 87 |
-
<height>10</height>
|
| 88 |
-
</size>
|
| 89 |
-
</property>
|
| 90 |
-
</spacer>
|
| 91 |
-
</item>
|
| 92 |
-
<item row="0" column="0" colspan="3">
|
| 93 |
-
<spacer name="verticalSpacer_5">
|
| 94 |
-
<property name="orientation">
|
| 95 |
-
<enum>Qt::Vertical</enum>
|
| 96 |
-
</property>
|
| 97 |
-
<property name="sizeType">
|
| 98 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 99 |
-
</property>
|
| 100 |
-
<property name="sizeHint" stdset="0">
|
| 101 |
-
<size>
|
| 102 |
-
<width>10</width>
|
| 103 |
-
<height>10</height>
|
| 104 |
-
</size>
|
| 105 |
-
</property>
|
| 106 |
-
</spacer>
|
| 107 |
-
</item>
|
| 108 |
-
<item row="2" column="0" colspan="3">
|
| 109 |
-
<widget class="QTableView" name="ncbi_table">
|
| 110 |
-
<property name="layoutDirection">
|
| 111 |
-
<enum>Qt::LeftToRight</enum>
|
| 112 |
-
</property>
|
| 113 |
-
</widget>
|
| 114 |
-
</item>
|
| 115 |
-
<item row="1" column="2" alignment="Qt::AlignRight">
|
| 116 |
-
<widget class="QCheckBox" name="all_rows">
|
| 117 |
-
<property name="text">
|
| 118 |
-
<string>Select all rows</string>
|
| 119 |
-
</property>
|
| 120 |
-
</widget>
|
| 121 |
-
</item>
|
| 122 |
-
</layout>
|
| 123 |
-
</widget>
|
| 124 |
-
</item>
|
| 125 |
-
<item row="0" column="1">
|
| 126 |
-
<widget class="QLabel" name="label_2">
|
| 127 |
-
<property name="text">
|
| 128 |
-
<string><html><head/><body><p><span style=" color:#ff0000;">* Required</span></p></body></html></string>
|
| 129 |
-
</property>
|
| 130 |
-
<property name="alignment">
|
| 131 |
-
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
| 132 |
-
</property>
|
| 133 |
-
</widget>
|
| 134 |
-
</item>
|
| 135 |
-
<item row="1" column="0" colspan="2">
|
| 136 |
-
<widget class="Line" name="line">
|
| 137 |
-
<property name="orientation">
|
| 138 |
-
<enum>Qt::Horizontal</enum>
|
| 139 |
-
</property>
|
| 140 |
-
</widget>
|
| 141 |
-
</item>
|
| 142 |
-
<item row="2" column="0">
|
| 143 |
-
<widget class="QGroupBox" name="Step1">
|
| 144 |
-
<property name="sizePolicy">
|
| 145 |
-
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
| 146 |
-
<horstretch>0</horstretch>
|
| 147 |
-
<verstretch>0</verstretch>
|
| 148 |
-
</sizepolicy>
|
| 149 |
-
</property>
|
| 150 |
-
<property name="title">
|
| 151 |
-
<string>Step 1: Input Search Options</string>
|
| 152 |
-
</property>
|
| 153 |
-
<layout class="QGridLayout" name="gridLayout_4">
|
| 154 |
-
<item row="2" column="0">
|
| 155 |
-
<widget class="QLabel" name="label_3">
|
| 156 |
-
<property name="sizePolicy">
|
| 157 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 158 |
-
<horstretch>0</horstretch>
|
| 159 |
-
<verstretch>0</verstretch>
|
| 160 |
-
</sizepolicy>
|
| 161 |
-
</property>
|
| 162 |
-
<property name="text">
|
| 163 |
-
<string>Strain Designation</string>
|
| 164 |
-
</property>
|
| 165 |
-
</widget>
|
| 166 |
-
</item>
|
| 167 |
-
<item row="3" column="0">
|
| 168 |
-
<widget class="QLabel" name="label_4">
|
| 169 |
-
<property name="sizePolicy">
|
| 170 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 171 |
-
<horstretch>0</horstretch>
|
| 172 |
-
<verstretch>0</verstretch>
|
| 173 |
-
</sizepolicy>
|
| 174 |
-
</property>
|
| 175 |
-
<property name="toolTip">
|
| 176 |
-
<string><html><head/><body><p><span style=" font-size:12pt;">Number of search results to return.</span></p></body></html></string>
|
| 177 |
-
</property>
|
| 178 |
-
<property name="text">
|
| 179 |
-
<string>Return Max (Default = 100)</string>
|
| 180 |
-
</property>
|
| 181 |
-
</widget>
|
| 182 |
-
</item>
|
| 183 |
-
<item row="4" column="0">
|
| 184 |
-
<widget class="QLabel" name="label_5">
|
| 185 |
-
<property name="sizePolicy">
|
| 186 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 187 |
-
<horstretch>0</horstretch>
|
| 188 |
-
<verstretch>0</verstretch>
|
| 189 |
-
</sizepolicy>
|
| 190 |
-
</property>
|
| 191 |
-
<property name="toolTip">
|
| 192 |
-
<string><html><head/><body><p><span style=" font-size:12pt;">Only display entries classified as &quot;Complete Genomes&quot; by NCBI.</span></p></body></html></string>
|
| 193 |
-
</property>
|
| 194 |
-
<property name="text">
|
| 195 |
-
<string>Complete Genomes Only</string>
|
| 196 |
-
</property>
|
| 197 |
-
</widget>
|
| 198 |
-
</item>
|
| 199 |
-
<item row="1" column="0">
|
| 200 |
-
<widget class="QLabel" name="label">
|
| 201 |
-
<property name="sizePolicy">
|
| 202 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 203 |
-
<horstretch>0</horstretch>
|
| 204 |
-
<verstretch>0</verstretch>
|
| 205 |
-
</sizepolicy>
|
| 206 |
-
</property>
|
| 207 |
-
<property name="text">
|
| 208 |
-
<string><html><head/><body><p><span style=" color:#ff0000;">* </span>Organism</p></body></html></string>
|
| 209 |
-
</property>
|
| 210 |
-
</widget>
|
| 211 |
-
</item>
|
| 212 |
-
<item row="1" column="1">
|
| 213 |
-
<widget class="QLineEdit" name="organism_line_edit">
|
| 214 |
-
<property name="placeholderText">
|
| 215 |
-
<string>Ex. Escherichia coli</string>
|
| 216 |
-
</property>
|
| 217 |
-
</widget>
|
| 218 |
-
</item>
|
| 219 |
-
<item row="2" column="1">
|
| 220 |
-
<widget class="QLineEdit" name="infra_name_line_edit">
|
| 221 |
-
<property name="placeholderText">
|
| 222 |
-
<string>Ex. K-12</string>
|
| 223 |
-
</property>
|
| 224 |
-
</widget>
|
| 225 |
-
</item>
|
| 226 |
-
<item row="3" column="1">
|
| 227 |
-
<widget class="QLineEdit" name="ret_max_line_edit">
|
| 228 |
-
<property name="text">
|
| 229 |
-
<string>100</string>
|
| 230 |
-
</property>
|
| 231 |
-
</widget>
|
| 232 |
-
</item>
|
| 233 |
-
<item row="4" column="1">
|
| 234 |
-
<widget class="QCheckBox" name="yes_box">
|
| 235 |
-
<property name="text">
|
| 236 |
-
<string/>
|
| 237 |
-
</property>
|
| 238 |
-
</widget>
|
| 239 |
-
</item>
|
| 240 |
-
<item row="5" column="0" colspan="2">
|
| 241 |
-
<spacer name="verticalSpacer_3">
|
| 242 |
-
<property name="orientation">
|
| 243 |
-
<enum>Qt::Vertical</enum>
|
| 244 |
-
</property>
|
| 245 |
-
<property name="sizeType">
|
| 246 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 247 |
-
</property>
|
| 248 |
-
<property name="sizeHint" stdset="0">
|
| 249 |
-
<size>
|
| 250 |
-
<width>10</width>
|
| 251 |
-
<height>10</height>
|
| 252 |
-
</size>
|
| 253 |
-
</property>
|
| 254 |
-
</spacer>
|
| 255 |
-
</item>
|
| 256 |
-
<item row="0" column="0" colspan="2">
|
| 257 |
-
<spacer name="verticalSpacer_4">
|
| 258 |
-
<property name="orientation">
|
| 259 |
-
<enum>Qt::Vertical</enum>
|
| 260 |
-
</property>
|
| 261 |
-
<property name="sizeType">
|
| 262 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 263 |
-
</property>
|
| 264 |
-
<property name="sizeHint" stdset="0">
|
| 265 |
-
<size>
|
| 266 |
-
<width>10</width>
|
| 267 |
-
<height>10</height>
|
| 268 |
-
</size>
|
| 269 |
-
</property>
|
| 270 |
-
</spacer>
|
| 271 |
-
</item>
|
| 272 |
-
</layout>
|
| 273 |
-
</widget>
|
| 274 |
-
</item>
|
| 275 |
-
<item row="0" column="0">
|
| 276 |
-
<widget class="QLabel" name="title">
|
| 277 |
-
<property name="sizePolicy">
|
| 278 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 279 |
-
<horstretch>0</horstretch>
|
| 280 |
-
<verstretch>0</verstretch>
|
| 281 |
-
</sizepolicy>
|
| 282 |
-
</property>
|
| 283 |
-
<property name="text">
|
| 284 |
-
<string>NCBI Tool</string>
|
| 285 |
-
</property>
|
| 286 |
-
</widget>
|
| 287 |
-
</item>
|
| 288 |
-
<item row="2" column="1">
|
| 289 |
-
<widget class="QGroupBox" name="Step3">
|
| 290 |
-
<property name="sizePolicy">
|
| 291 |
-
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
| 292 |
-
<horstretch>0</horstretch>
|
| 293 |
-
<verstretch>0</verstretch>
|
| 294 |
-
</sizepolicy>
|
| 295 |
-
</property>
|
| 296 |
-
<property name="title">
|
| 297 |
-
<string>Step 3: Download Files</string>
|
| 298 |
-
</property>
|
| 299 |
-
<layout class="QGridLayout" name="gridLayout_5">
|
| 300 |
-
<item row="7" column="0" colspan="3">
|
| 301 |
-
<spacer name="verticalSpacer_8">
|
| 302 |
-
<property name="orientation">
|
| 303 |
-
<enum>Qt::Vertical</enum>
|
| 304 |
-
</property>
|
| 305 |
-
<property name="sizeType">
|
| 306 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 307 |
-
</property>
|
| 308 |
-
<property name="sizeHint" stdset="0">
|
| 309 |
-
<size>
|
| 310 |
-
<width>10</width>
|
| 311 |
-
<height>10</height>
|
| 312 |
-
</size>
|
| 313 |
-
</property>
|
| 314 |
-
</spacer>
|
| 315 |
-
</item>
|
| 316 |
-
<item row="3" column="0">
|
| 317 |
-
<widget class="QPushButton" name="download_button">
|
| 318 |
-
<property name="minimumSize">
|
| 319 |
-
<size>
|
| 320 |
-
<width>125</width>
|
| 321 |
-
<height>0</height>
|
| 322 |
-
</size>
|
| 323 |
-
</property>
|
| 324 |
-
<property name="text">
|
| 325 |
-
<string>Download Files</string>
|
| 326 |
-
</property>
|
| 327 |
-
</widget>
|
| 328 |
-
</item>
|
| 329 |
-
<item row="2" column="0">
|
| 330 |
-
<widget class="QLabel" name="label_7">
|
| 331 |
-
<property name="toolTip">
|
| 332 |
-
<string><html><head/><body><p><span style=" font-size:12pt;">These are the file types that you are choosing to download. GBFF = annotation file, FASTA/FNA = genomic sequence file.</span></p></body></html></string>
|
| 333 |
-
</property>
|
| 334 |
-
<property name="text">
|
| 335 |
-
<string><html><head/><body><p><span style=" color:#ff0000;">*</span> File Types:</p></body></html></string>
|
| 336 |
-
</property>
|
| 337 |
-
</widget>
|
| 338 |
-
</item>
|
| 339 |
-
<item row="1" column="2">
|
| 340 |
-
<widget class="QRadioButton" name="genbank_checkbox">
|
| 341 |
-
<property name="text">
|
| 342 |
-
<string>GenBank</string>
|
| 343 |
-
</property>
|
| 344 |
-
<property name="checked">
|
| 345 |
-
<bool>false</bool>
|
| 346 |
-
</property>
|
| 347 |
-
</widget>
|
| 348 |
-
</item>
|
| 349 |
-
<item row="1" column="1">
|
| 350 |
-
<widget class="QRadioButton" name="refseq_checkbox">
|
| 351 |
-
<property name="text">
|
| 352 |
-
<string>RefSeq</string>
|
| 353 |
-
</property>
|
| 354 |
-
<property name="checked">
|
| 355 |
-
<bool>true</bool>
|
| 356 |
-
</property>
|
| 357 |
-
</widget>
|
| 358 |
-
</item>
|
| 359 |
-
<item row="0" column="0" colspan="3">
|
| 360 |
-
<spacer name="verticalSpacer_7">
|
| 361 |
-
<property name="orientation">
|
| 362 |
-
<enum>Qt::Vertical</enum>
|
| 363 |
-
</property>
|
| 364 |
-
<property name="sizeType">
|
| 365 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 366 |
-
</property>
|
| 367 |
-
<property name="sizeHint" stdset="0">
|
| 368 |
-
<size>
|
| 369 |
-
<width>10</width>
|
| 370 |
-
<height>10</height>
|
| 371 |
-
</size>
|
| 372 |
-
</property>
|
| 373 |
-
</spacer>
|
| 374 |
-
</item>
|
| 375 |
-
<item row="1" column="0">
|
| 376 |
-
<widget class="QLabel" name="label_6">
|
| 377 |
-
<property name="toolTip">
|
| 378 |
-
<string><html><head/><body><p><span style=" font-size:12pt;">These are the databases that files are downloaded from. Refseq is considered the gold standard for annotations and assemblies. It is HIGHLY recommended to use Refseq if available for the genome of interest.</span></p></body></html></string>
|
| 379 |
-
</property>
|
| 380 |
-
<property name="text">
|
| 381 |
-
<string><html><head/><body><p><span style=" color:#ff0000;">*</span> Collections:</p></body></html></string>
|
| 382 |
-
</property>
|
| 383 |
-
</widget>
|
| 384 |
-
</item>
|
| 385 |
-
<item row="3" column="1" colspan="2">
|
| 386 |
-
<widget class="QLabel" name="progressLabel">
|
| 387 |
-
<property name="text">
|
| 388 |
-
<string/>
|
| 389 |
-
</property>
|
| 390 |
-
</widget>
|
| 391 |
-
</item>
|
| 392 |
-
<item row="4" column="0" colspan="3">
|
| 393 |
-
<widget class="QProgressBar" name="progressBar">
|
| 394 |
-
<property name="value">
|
| 395 |
-
<number>24</number>
|
| 396 |
-
</property>
|
| 397 |
-
</widget>
|
| 398 |
-
</item>
|
| 399 |
-
<item row="6" column="0" colspan="3">
|
| 400 |
-
<layout class="QFormLayout" name="formLayout">
|
| 401 |
-
<property name="fieldGrowthPolicy">
|
| 402 |
-
<enum>QFormLayout::ExpandingFieldsGrow</enum>
|
| 403 |
-
</property>
|
| 404 |
-
</layout>
|
| 405 |
-
</item>
|
| 406 |
-
<item row="2" column="1">
|
| 407 |
-
<widget class="QCheckBox" name="fna_checkbox">
|
| 408 |
-
<property name="text">
|
| 409 |
-
<string>FASTA/FNA</string>
|
| 410 |
-
</property>
|
| 411 |
-
<property name="checked">
|
| 412 |
-
<bool>true</bool>
|
| 413 |
-
</property>
|
| 414 |
-
</widget>
|
| 415 |
-
</item>
|
| 416 |
-
<item row="2" column="2">
|
| 417 |
-
<widget class="QCheckBox" name="gbff_checkbox">
|
| 418 |
-
<property name="text">
|
| 419 |
-
<string>GBFF</string>
|
| 420 |
-
</property>
|
| 421 |
-
<property name="checked">
|
| 422 |
-
<bool>true</bool>
|
| 423 |
-
</property>
|
| 424 |
-
</widget>
|
| 425 |
-
</item>
|
| 426 |
-
</layout>
|
| 427 |
-
</widget>
|
| 428 |
-
</item>
|
| 429 |
-
<item row="4" column="0" alignment="Qt::AlignLeft">
|
| 430 |
-
<widget class="QPushButton" name="back_button">
|
| 431 |
-
<property name="minimumSize">
|
| 432 |
-
<size>
|
| 433 |
-
<width>125</width>
|
| 434 |
-
<height>0</height>
|
| 435 |
-
</size>
|
| 436 |
-
</property>
|
| 437 |
-
<property name="text">
|
| 438 |
-
<string>Back</string>
|
| 439 |
-
</property>
|
| 440 |
-
</widget>
|
| 441 |
-
</item>
|
| 442 |
-
</layout>
|
| 443 |
-
</item>
|
| 444 |
-
<item row="1" column="2">
|
| 445 |
-
<spacer name="horizontalSpacer_2">
|
| 446 |
-
<property name="orientation">
|
| 447 |
-
<enum>Qt::Horizontal</enum>
|
| 448 |
-
</property>
|
| 449 |
-
<property name="sizeType">
|
| 450 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 451 |
-
</property>
|
| 452 |
-
<property name="sizeHint" stdset="0">
|
| 453 |
-
<size>
|
| 454 |
-
<width>20</width>
|
| 455 |
-
<height>20</height>
|
| 456 |
-
</size>
|
| 457 |
-
</property>
|
| 458 |
-
</spacer>
|
| 459 |
-
</item>
|
| 460 |
-
<item row="0" column="1">
|
| 461 |
-
<spacer name="verticalSpacer_2">
|
| 462 |
-
<property name="orientation">
|
| 463 |
-
<enum>Qt::Vertical</enum>
|
| 464 |
-
</property>
|
| 465 |
-
<property name="sizeType">
|
| 466 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 467 |
-
</property>
|
| 468 |
-
<property name="sizeHint" stdset="0">
|
| 469 |
-
<size>
|
| 470 |
-
<width>20</width>
|
| 471 |
-
<height>20</height>
|
| 472 |
-
</size>
|
| 473 |
-
</property>
|
| 474 |
-
</spacer>
|
| 475 |
-
</item>
|
| 476 |
-
</layout>
|
| 477 |
-
</widget>
|
| 478 |
-
<widget class="QStatusBar" name="statusbar"/>
|
| 479 |
-
</widget>
|
| 480 |
-
<tabstops>
|
| 481 |
-
<tabstop>organism_line_edit</tabstop>
|
| 482 |
-
<tabstop>infra_name_line_edit</tabstop>
|
| 483 |
-
<tabstop>ret_max_line_edit</tabstop>
|
| 484 |
-
<tabstop>yes_box</tabstop>
|
| 485 |
-
<tabstop>search_button</tabstop>
|
| 486 |
-
<tabstop>all_rows</tabstop>
|
| 487 |
-
<tabstop>ncbi_table</tabstop>
|
| 488 |
-
<tabstop>genbank_checkbox</tabstop>
|
| 489 |
-
<tabstop>refseq_checkbox</tabstop>
|
| 490 |
-
<tabstop>download_button</tabstop>
|
| 491 |
-
<tabstop>back_button</tabstop>
|
| 492 |
-
</tabstops>
|
| 493 |
-
<resources/>
|
| 494 |
-
<connections/>
|
| 495 |
-
</ui>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,7 +1,7 @@
|
|
| 1 |
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
<ui version="4.0">
|
| 3 |
<class>MainWindow</class>
|
| 4 |
-
<widget class="
|
| 5 |
<property name="geometry">
|
| 6 |
<rect>
|
| 7 |
<x>0</x>
|
|
|
|
| 1 |
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
<ui version="4.0">
|
| 3 |
<class>MainWindow</class>
|
| 4 |
+
<widget class="QWidget" name="MainWindow">
|
| 5 |
<property name="geometry">
|
| 6 |
<rect>
|
| 7 |
<x>0</x>
|
|
@@ -1,823 +0,0 @@
|
|
| 1 |
-
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
-
<ui version="4.0">
|
| 3 |
-
<class>MainWindow</class>
|
| 4 |
-
<widget class="QMainWindow" name="MainWindow">
|
| 5 |
-
<property name="geometry">
|
| 6 |
-
<rect>
|
| 7 |
-
<x>0</x>
|
| 8 |
-
<y>0</y>
|
| 9 |
-
<width>811</width>
|
| 10 |
-
<height>862</height>
|
| 11 |
-
</rect>
|
| 12 |
-
</property>
|
| 13 |
-
<property name="font">
|
| 14 |
-
<font>
|
| 15 |
-
<pointsize>12</pointsize>
|
| 16 |
-
</font>
|
| 17 |
-
</property>
|
| 18 |
-
<property name="windowTitle">
|
| 19 |
-
<string>MainWindow</string>
|
| 20 |
-
</property>
|
| 21 |
-
<widget class="QWidget" name="centralwidget">
|
| 22 |
-
<layout class="QGridLayout" name="gridLayout_3">
|
| 23 |
-
<item row="2" column="1" colspan="2">
|
| 24 |
-
<widget class="QGroupBox" name="grpStep2">
|
| 25 |
-
<property name="sizePolicy">
|
| 26 |
-
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
| 27 |
-
<horstretch>0</horstretch>
|
| 28 |
-
<verstretch>0</verstretch>
|
| 29 |
-
</sizepolicy>
|
| 30 |
-
</property>
|
| 31 |
-
<property name="minimumSize">
|
| 32 |
-
<size>
|
| 33 |
-
<width>0</width>
|
| 34 |
-
<height>0</height>
|
| 35 |
-
</size>
|
| 36 |
-
</property>
|
| 37 |
-
<property name="maximumSize">
|
| 38 |
-
<size>
|
| 39 |
-
<width>16777215</width>
|
| 40 |
-
<height>16777215</height>
|
| 41 |
-
</size>
|
| 42 |
-
</property>
|
| 43 |
-
<property name="styleSheet">
|
| 44 |
-
<string notr="true"/>
|
| 45 |
-
</property>
|
| 46 |
-
<property name="title">
|
| 47 |
-
<string>Step 2: Select File and Add Job to Queue</string>
|
| 48 |
-
</property>
|
| 49 |
-
<layout class="QGridLayout" name="gridLayout_4">
|
| 50 |
-
<property name="topMargin">
|
| 51 |
-
<number>15</number>
|
| 52 |
-
</property>
|
| 53 |
-
<item row="0" column="0">
|
| 54 |
-
<layout class="QVBoxLayout" name="verticalLayout_5">
|
| 55 |
-
<item>
|
| 56 |
-
<widget class="QLabel" name="lblSelectLocalFile">
|
| 57 |
-
<property name="sizePolicy">
|
| 58 |
-
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 59 |
-
<horstretch>0</horstretch>
|
| 60 |
-
<verstretch>0</verstretch>
|
| 61 |
-
</sizepolicy>
|
| 62 |
-
</property>
|
| 63 |
-
<property name="text">
|
| 64 |
-
<string><html><head/><body><p><span style=" color:#fc0107;">* </span>Select Local FASTA/FNA File:</p></body></html></string>
|
| 65 |
-
</property>
|
| 66 |
-
</widget>
|
| 67 |
-
</item>
|
| 68 |
-
<item>
|
| 69 |
-
<layout class="QHBoxLayout" name="horizontalLayout">
|
| 70 |
-
<item>
|
| 71 |
-
<layout class="QHBoxLayout" name="horizontalLayout_10">
|
| 72 |
-
<property name="sizeConstraint">
|
| 73 |
-
<enum>QLayout::SetMinimumSize</enum>
|
| 74 |
-
</property>
|
| 75 |
-
<item>
|
| 76 |
-
<widget class="QPushButton" name="pbtnNCBISearch">
|
| 77 |
-
<property name="sizePolicy">
|
| 78 |
-
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 79 |
-
<horstretch>0</horstretch>
|
| 80 |
-
<verstretch>0</verstretch>
|
| 81 |
-
</sizepolicy>
|
| 82 |
-
</property>
|
| 83 |
-
<property name="minimumSize">
|
| 84 |
-
<size>
|
| 85 |
-
<width>0</width>
|
| 86 |
-
<height>0</height>
|
| 87 |
-
</size>
|
| 88 |
-
</property>
|
| 89 |
-
<property name="text">
|
| 90 |
-
<string>NCBI Search</string>
|
| 91 |
-
</property>
|
| 92 |
-
</widget>
|
| 93 |
-
</item>
|
| 94 |
-
<item>
|
| 95 |
-
<widget class="QPushButton" name="pbtnBrowseFile">
|
| 96 |
-
<property name="sizePolicy">
|
| 97 |
-
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 98 |
-
<horstretch>0</horstretch>
|
| 99 |
-
<verstretch>0</verstretch>
|
| 100 |
-
</sizepolicy>
|
| 101 |
-
</property>
|
| 102 |
-
<property name="minimumSize">
|
| 103 |
-
<size>
|
| 104 |
-
<width>0</width>
|
| 105 |
-
<height>0</height>
|
| 106 |
-
</size>
|
| 107 |
-
</property>
|
| 108 |
-
<property name="text">
|
| 109 |
-
<string>Browse File</string>
|
| 110 |
-
</property>
|
| 111 |
-
</widget>
|
| 112 |
-
</item>
|
| 113 |
-
</layout>
|
| 114 |
-
</item>
|
| 115 |
-
<item>
|
| 116 |
-
<widget class="QLineEdit" name="ledSelectedFile">
|
| 117 |
-
<property name="sizePolicy">
|
| 118 |
-
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 119 |
-
<horstretch>0</horstretch>
|
| 120 |
-
<verstretch>0</verstretch>
|
| 121 |
-
</sizepolicy>
|
| 122 |
-
</property>
|
| 123 |
-
<property name="inputMask">
|
| 124 |
-
<string/>
|
| 125 |
-
</property>
|
| 126 |
-
<property name="text">
|
| 127 |
-
<string/>
|
| 128 |
-
</property>
|
| 129 |
-
<property name="readOnly">
|
| 130 |
-
<bool>true</bool>
|
| 131 |
-
</property>
|
| 132 |
-
<property name="placeholderText">
|
| 133 |
-
<string>Selected FASTA/FNA File</string>
|
| 134 |
-
</property>
|
| 135 |
-
</widget>
|
| 136 |
-
</item>
|
| 137 |
-
<item>
|
| 138 |
-
<layout class="QHBoxLayout" name="horizontalLayout_9">
|
| 139 |
-
<property name="sizeConstraint">
|
| 140 |
-
<enum>QLayout::SetMinimumSize</enum>
|
| 141 |
-
</property>
|
| 142 |
-
</layout>
|
| 143 |
-
</item>
|
| 144 |
-
</layout>
|
| 145 |
-
</item>
|
| 146 |
-
<item>
|
| 147 |
-
<layout class="QHBoxLayout" name="horizontalLayout_11">
|
| 148 |
-
<item>
|
| 149 |
-
<widget class="QPushButton" name="pbtnAddJob">
|
| 150 |
-
<property name="sizePolicy">
|
| 151 |
-
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 152 |
-
<horstretch>0</horstretch>
|
| 153 |
-
<verstretch>0</verstretch>
|
| 154 |
-
</sizepolicy>
|
| 155 |
-
</property>
|
| 156 |
-
<property name="minimumSize">
|
| 157 |
-
<size>
|
| 158 |
-
<width>0</width>
|
| 159 |
-
<height>0</height>
|
| 160 |
-
</size>
|
| 161 |
-
</property>
|
| 162 |
-
<property name="text">
|
| 163 |
-
<string>Add Job to Queue</string>
|
| 164 |
-
</property>
|
| 165 |
-
</widget>
|
| 166 |
-
</item>
|
| 167 |
-
</layout>
|
| 168 |
-
</item>
|
| 169 |
-
</layout>
|
| 170 |
-
</item>
|
| 171 |
-
</layout>
|
| 172 |
-
</widget>
|
| 173 |
-
</item>
|
| 174 |
-
<item row="1" column="1" colspan="2">
|
| 175 |
-
<widget class="QGroupBox" name="grpStep1">
|
| 176 |
-
<property name="sizePolicy">
|
| 177 |
-
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
| 178 |
-
<horstretch>0</horstretch>
|
| 179 |
-
<verstretch>0</verstretch>
|
| 180 |
-
</sizepolicy>
|
| 181 |
-
</property>
|
| 182 |
-
<property name="minimumSize">
|
| 183 |
-
<size>
|
| 184 |
-
<width>0</width>
|
| 185 |
-
<height>0</height>
|
| 186 |
-
</size>
|
| 187 |
-
</property>
|
| 188 |
-
<property name="maximumSize">
|
| 189 |
-
<size>
|
| 190 |
-
<width>16777215</width>
|
| 191 |
-
<height>16777215</height>
|
| 192 |
-
</size>
|
| 193 |
-
</property>
|
| 194 |
-
<property name="font">
|
| 195 |
-
<font>
|
| 196 |
-
<family>Arial</family>
|
| 197 |
-
<pointsize>12</pointsize>
|
| 198 |
-
<weight>50</weight>
|
| 199 |
-
<italic>false</italic>
|
| 200 |
-
<bold>false</bold>
|
| 201 |
-
</font>
|
| 202 |
-
</property>
|
| 203 |
-
<property name="styleSheet">
|
| 204 |
-
<string notr="true"/>
|
| 205 |
-
</property>
|
| 206 |
-
<property name="title">
|
| 207 |
-
<string>Step 1: Input Organism Info</string>
|
| 208 |
-
</property>
|
| 209 |
-
<layout class="QGridLayout" name="gridLayout_2">
|
| 210 |
-
<property name="topMargin">
|
| 211 |
-
<number>15</number>
|
| 212 |
-
</property>
|
| 213 |
-
<item row="3" column="4" colspan="2">
|
| 214 |
-
<layout class="QHBoxLayout" name="chkBoxLayout">
|
| 215 |
-
<item>
|
| 216 |
-
<widget class="QCheckBox" name="chckGenerateRepeats">
|
| 217 |
-
<property name="sizePolicy">
|
| 218 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 219 |
-
<horstretch>0</horstretch>
|
| 220 |
-
<verstretch>0</verstretch>
|
| 221 |
-
</sizepolicy>
|
| 222 |
-
</property>
|
| 223 |
-
<property name="text">
|
| 224 |
-
<string>Generate Repeats</string>
|
| 225 |
-
</property>
|
| 226 |
-
<property name="checked">
|
| 227 |
-
<bool>true</bool>
|
| 228 |
-
</property>
|
| 229 |
-
</widget>
|
| 230 |
-
</item>
|
| 231 |
-
<item>
|
| 232 |
-
<widget class="QCheckBox" name="chckMultithreading">
|
| 233 |
-
<property name="sizePolicy">
|
| 234 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 235 |
-
<horstretch>0</horstretch>
|
| 236 |
-
<verstretch>0</verstretch>
|
| 237 |
-
</sizepolicy>
|
| 238 |
-
</property>
|
| 239 |
-
<property name="text">
|
| 240 |
-
<string>Multithreading</string>
|
| 241 |
-
</property>
|
| 242 |
-
<property name="checked">
|
| 243 |
-
<bool>true</bool>
|
| 244 |
-
</property>
|
| 245 |
-
</widget>
|
| 246 |
-
</item>
|
| 247 |
-
</layout>
|
| 248 |
-
</item>
|
| 249 |
-
<item row="1" column="0">
|
| 250 |
-
<widget class="QLabel" name="lblRequiredStrain">
|
| 251 |
-
<property name="text">
|
| 252 |
-
<string><html><head/><body><p><span style=" color:#fc0107;">*</span></p></body></html></string>
|
| 253 |
-
</property>
|
| 254 |
-
</widget>
|
| 255 |
-
</item>
|
| 256 |
-
<item row="1" column="1">
|
| 257 |
-
<widget class="QLabel" name="lblStrain">
|
| 258 |
-
<property name="sizePolicy">
|
| 259 |
-
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
|
| 260 |
-
<horstretch>0</horstretch>
|
| 261 |
-
<verstretch>0</verstretch>
|
| 262 |
-
</sizepolicy>
|
| 263 |
-
</property>
|
| 264 |
-
<property name="toolTip">
|
| 265 |
-
<string/>
|
| 266 |
-
</property>
|
| 267 |
-
<property name="text">
|
| 268 |
-
<string>Strain:</string>
|
| 269 |
-
</property>
|
| 270 |
-
</widget>
|
| 271 |
-
</item>
|
| 272 |
-
<item row="2" column="2">
|
| 273 |
-
<widget class="QComboBox" name="cmbEndonuclease">
|
| 274 |
-
<property name="sizePolicy">
|
| 275 |
-
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 276 |
-
<horstretch>0</horstretch>
|
| 277 |
-
<verstretch>0</verstretch>
|
| 278 |
-
</sizepolicy>
|
| 279 |
-
</property>
|
| 280 |
-
<property name="maximumSize">
|
| 281 |
-
<size>
|
| 282 |
-
<width>16777215</width>
|
| 283 |
-
<height>16777215</height>
|
| 284 |
-
</size>
|
| 285 |
-
</property>
|
| 286 |
-
</widget>
|
| 287 |
-
</item>
|
| 288 |
-
<item row="3" column="1">
|
| 289 |
-
<widget class="QLabel" name="lblOrganismCode">
|
| 290 |
-
<property name="sizePolicy">
|
| 291 |
-
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
| 292 |
-
<horstretch>0</horstretch>
|
| 293 |
-
<verstretch>0</verstretch>
|
| 294 |
-
</sizepolicy>
|
| 295 |
-
</property>
|
| 296 |
-
<property name="toolTip">
|
| 297 |
-
<string>The organism code is the manner in which CASPER will label its data files
|
| 298 |
-
and references for the organism you are importing here.</string>
|
| 299 |
-
</property>
|
| 300 |
-
<property name="text">
|
| 301 |
-
<string>Organism Code:</string>
|
| 302 |
-
</property>
|
| 303 |
-
</widget>
|
| 304 |
-
</item>
|
| 305 |
-
<item row="0" column="1">
|
| 306 |
-
<widget class="QLabel" name="lblOrganismName">
|
| 307 |
-
<property name="sizePolicy">
|
| 308 |
-
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
| 309 |
-
<horstretch>0</horstretch>
|
| 310 |
-
<verstretch>0</verstretch>
|
| 311 |
-
</sizepolicy>
|
| 312 |
-
</property>
|
| 313 |
-
<property name="text">
|
| 314 |
-
<string>Organism Name:</string>
|
| 315 |
-
</property>
|
| 316 |
-
</widget>
|
| 317 |
-
</item>
|
| 318 |
-
<item row="2" column="4">
|
| 319 |
-
<widget class="QLabel" name="lblThreeLength">
|
| 320 |
-
<property name="sizePolicy">
|
| 321 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
| 322 |
-
<horstretch>0</horstretch>
|
| 323 |
-
<verstretch>0</verstretch>
|
| 324 |
-
</sizepolicy>
|
| 325 |
-
</property>
|
| 326 |
-
<property name="toolTip">
|
| 327 |
-
<string><html><head/><body><p>Length in nucleotides of gRNA sequence downstream (3')</p><p>of the seed.</p></body></html></string>
|
| 328 |
-
</property>
|
| 329 |
-
<property name="text">
|
| 330 |
-
<string>3' Length:</string>
|
| 331 |
-
</property>
|
| 332 |
-
</widget>
|
| 333 |
-
</item>
|
| 334 |
-
<item row="0" column="2">
|
| 335 |
-
<widget class="QLineEdit" name="ledOrganismName">
|
| 336 |
-
<property name="maximumSize">
|
| 337 |
-
<size>
|
| 338 |
-
<width>16777215</width>
|
| 339 |
-
<height>16777215</height>
|
| 340 |
-
</size>
|
| 341 |
-
</property>
|
| 342 |
-
</widget>
|
| 343 |
-
</item>
|
| 344 |
-
<item row="2" column="1">
|
| 345 |
-
<widget class="QLabel" name="lblEndonuclease">
|
| 346 |
-
<property name="sizePolicy">
|
| 347 |
-
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
|
| 348 |
-
<horstretch>0</horstretch>
|
| 349 |
-
<verstretch>0</verstretch>
|
| 350 |
-
</sizepolicy>
|
| 351 |
-
</property>
|
| 352 |
-
<property name="toolTip">
|
| 353 |
-
<string>The Cas endonuclease selected determines the gRNA
|
| 354 |
-
search parameters</string>
|
| 355 |
-
</property>
|
| 356 |
-
<property name="text">
|
| 357 |
-
<string>Endonuclease:</string>
|
| 358 |
-
</property>
|
| 359 |
-
</widget>
|
| 360 |
-
</item>
|
| 361 |
-
<item row="3" column="2">
|
| 362 |
-
<widget class="QLineEdit" name="ledOrganismCode">
|
| 363 |
-
<property name="maximumSize">
|
| 364 |
-
<size>
|
| 365 |
-
<width>16777215</width>
|
| 366 |
-
<height>16777215</height>
|
| 367 |
-
</size>
|
| 368 |
-
</property>
|
| 369 |
-
</widget>
|
| 370 |
-
</item>
|
| 371 |
-
<item row="1" column="4">
|
| 372 |
-
<widget class="QLabel" name="lblFiveLength">
|
| 373 |
-
<property name="sizePolicy">
|
| 374 |
-
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
|
| 375 |
-
<horstretch>0</horstretch>
|
| 376 |
-
<verstretch>0</verstretch>
|
| 377 |
-
</sizepolicy>
|
| 378 |
-
</property>
|
| 379 |
-
<property name="toolTip">
|
| 380 |
-
<string><html><head/><body><p>Length in nucleotides of gRNA sequence upstream (5')</p><p>of the seed.</p></body></html></string>
|
| 381 |
-
</property>
|
| 382 |
-
<property name="text">
|
| 383 |
-
<string>5' Length:</string>
|
| 384 |
-
</property>
|
| 385 |
-
</widget>
|
| 386 |
-
</item>
|
| 387 |
-
<item row="0" column="5">
|
| 388 |
-
<widget class="QLineEdit" name="ledSeedLength">
|
| 389 |
-
<property name="maximumSize">
|
| 390 |
-
<size>
|
| 391 |
-
<width>16777215</width>
|
| 392 |
-
<height>16777215</height>
|
| 393 |
-
</size>
|
| 394 |
-
</property>
|
| 395 |
-
<property name="readOnly">
|
| 396 |
-
<bool>true</bool>
|
| 397 |
-
</property>
|
| 398 |
-
</widget>
|
| 399 |
-
</item>
|
| 400 |
-
<item row="1" column="5">
|
| 401 |
-
<widget class="QLineEdit" name="ledFiveLength">
|
| 402 |
-
<property name="maximumSize">
|
| 403 |
-
<size>
|
| 404 |
-
<width>16777215</width>
|
| 405 |
-
<height>16777215</height>
|
| 406 |
-
</size>
|
| 407 |
-
</property>
|
| 408 |
-
<property name="readOnly">
|
| 409 |
-
<bool>true</bool>
|
| 410 |
-
</property>
|
| 411 |
-
</widget>
|
| 412 |
-
</item>
|
| 413 |
-
<item row="2" column="0">
|
| 414 |
-
<widget class="QLabel" name="lblRequiredEndonuclease">
|
| 415 |
-
<property name="text">
|
| 416 |
-
<string><html><head/><body><p><span style=" color:#fc0107;">*</span></p></body></html></string>
|
| 417 |
-
</property>
|
| 418 |
-
</widget>
|
| 419 |
-
</item>
|
| 420 |
-
<item row="0" column="3">
|
| 421 |
-
<widget class="QLabel" name="lblRequiredSeedLength">
|
| 422 |
-
<property name="text">
|
| 423 |
-
<string><html><head/><body><p><span style=" color:#fc0107;">*</span></p></body></html></string>
|
| 424 |
-
</property>
|
| 425 |
-
</widget>
|
| 426 |
-
</item>
|
| 427 |
-
<item row="2" column="5">
|
| 428 |
-
<widget class="QLineEdit" name="ledThreeLength">
|
| 429 |
-
<property name="maximumSize">
|
| 430 |
-
<size>
|
| 431 |
-
<width>16777215</width>
|
| 432 |
-
<height>16777215</height>
|
| 433 |
-
</size>
|
| 434 |
-
</property>
|
| 435 |
-
<property name="readOnly">
|
| 436 |
-
<bool>true</bool>
|
| 437 |
-
</property>
|
| 438 |
-
</widget>
|
| 439 |
-
</item>
|
| 440 |
-
<item row="3" column="0">
|
| 441 |
-
<widget class="QLabel" name="lblRequiredOrganismCode">
|
| 442 |
-
<property name="text">
|
| 443 |
-
<string><html><head/><body><p><span style=" color:#fc0107;">*</span></p></body></html></string>
|
| 444 |
-
</property>
|
| 445 |
-
</widget>
|
| 446 |
-
</item>
|
| 447 |
-
<item row="1" column="2">
|
| 448 |
-
<widget class="QLineEdit" name="ledStrain">
|
| 449 |
-
<property name="maximumSize">
|
| 450 |
-
<size>
|
| 451 |
-
<width>16777215</width>
|
| 452 |
-
<height>16777215</height>
|
| 453 |
-
</size>
|
| 454 |
-
</property>
|
| 455 |
-
</widget>
|
| 456 |
-
</item>
|
| 457 |
-
<item row="0" column="4">
|
| 458 |
-
<widget class="QLabel" name="lblSeedLength">
|
| 459 |
-
<property name="sizePolicy">
|
| 460 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
| 461 |
-
<horstretch>0</horstretch>
|
| 462 |
-
<verstretch>0</verstretch>
|
| 463 |
-
</sizepolicy>
|
| 464 |
-
</property>
|
| 465 |
-
<property name="toolTip">
|
| 466 |
-
<string><html><head/><body><p>The length in nucleotides of the region of gRNA most crucial</p><p>for gRNA binding. CASPER uses this value to find repeated</p><p>gRNA sequences.</p></body></html></string>
|
| 467 |
-
</property>
|
| 468 |
-
<property name="text">
|
| 469 |
-
<string>Seed Length:</string>
|
| 470 |
-
</property>
|
| 471 |
-
</widget>
|
| 472 |
-
</item>
|
| 473 |
-
<item row="0" column="0">
|
| 474 |
-
<widget class="QLabel" name="lblRequiredOrganismName">
|
| 475 |
-
<property name="text">
|
| 476 |
-
<string><html><head/><body><p><span style=" color:#fc0107;">*</span></p></body></html></string>
|
| 477 |
-
</property>
|
| 478 |
-
</widget>
|
| 479 |
-
</item>
|
| 480 |
-
</layout>
|
| 481 |
-
</widget>
|
| 482 |
-
</item>
|
| 483 |
-
<item row="4" column="1" colspan="2">
|
| 484 |
-
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
| 485 |
-
<item alignment="Qt::AlignRight">
|
| 486 |
-
<widget class="QPushButton" name="pbtnResetForm">
|
| 487 |
-
<property name="sizePolicy">
|
| 488 |
-
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
| 489 |
-
<horstretch>0</horstretch>
|
| 490 |
-
<verstretch>0</verstretch>
|
| 491 |
-
</sizepolicy>
|
| 492 |
-
</property>
|
| 493 |
-
<property name="minimumSize">
|
| 494 |
-
<size>
|
| 495 |
-
<width>125</width>
|
| 496 |
-
<height>0</height>
|
| 497 |
-
</size>
|
| 498 |
-
</property>
|
| 499 |
-
<property name="maximumSize">
|
| 500 |
-
<size>
|
| 501 |
-
<width>16777215</width>
|
| 502 |
-
<height>16777215</height>
|
| 503 |
-
</size>
|
| 504 |
-
</property>
|
| 505 |
-
<property name="text">
|
| 506 |
-
<string>Reset Form</string>
|
| 507 |
-
</property>
|
| 508 |
-
</widget>
|
| 509 |
-
</item>
|
| 510 |
-
</layout>
|
| 511 |
-
</item>
|
| 512 |
-
<item row="0" column="1">
|
| 513 |
-
<widget class="QLabel" name="lblWindowHeading">
|
| 514 |
-
<property name="minimumSize">
|
| 515 |
-
<size>
|
| 516 |
-
<width>0</width>
|
| 517 |
-
<height>0</height>
|
| 518 |
-
</size>
|
| 519 |
-
</property>
|
| 520 |
-
<property name="maximumSize">
|
| 521 |
-
<size>
|
| 522 |
-
<width>16777215</width>
|
| 523 |
-
<height>50</height>
|
| 524 |
-
</size>
|
| 525 |
-
</property>
|
| 526 |
-
<property name="font">
|
| 527 |
-
<font>
|
| 528 |
-
<family>Arial</family>
|
| 529 |
-
<pointsize>12</pointsize>
|
| 530 |
-
<weight>75</weight>
|
| 531 |
-
<italic>false</italic>
|
| 532 |
-
<bold>true</bold>
|
| 533 |
-
</font>
|
| 534 |
-
</property>
|
| 535 |
-
<property name="styleSheet">
|
| 536 |
-
<string notr="true"/>
|
| 537 |
-
</property>
|
| 538 |
-
<property name="text">
|
| 539 |
-
<string>New Genome</string>
|
| 540 |
-
</property>
|
| 541 |
-
<property name="scaledContents">
|
| 542 |
-
<bool>false</bool>
|
| 543 |
-
</property>
|
| 544 |
-
</widget>
|
| 545 |
-
</item>
|
| 546 |
-
<item row="3" column="1" colspan="2">
|
| 547 |
-
<widget class="QGroupBox" name="Step3">
|
| 548 |
-
<property name="sizePolicy">
|
| 549 |
-
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
| 550 |
-
<horstretch>0</horstretch>
|
| 551 |
-
<verstretch>0</verstretch>
|
| 552 |
-
</sizepolicy>
|
| 553 |
-
</property>
|
| 554 |
-
<property name="minimumSize">
|
| 555 |
-
<size>
|
| 556 |
-
<width>0</width>
|
| 557 |
-
<height>0</height>
|
| 558 |
-
</size>
|
| 559 |
-
</property>
|
| 560 |
-
<property name="maximumSize">
|
| 561 |
-
<size>
|
| 562 |
-
<width>16777215</width>
|
| 563 |
-
<height>16777215</height>
|
| 564 |
-
</size>
|
| 565 |
-
</property>
|
| 566 |
-
<property name="font">
|
| 567 |
-
<font>
|
| 568 |
-
<family>Arial</family>
|
| 569 |
-
<pointsize>12</pointsize>
|
| 570 |
-
<weight>50</weight>
|
| 571 |
-
<italic>false</italic>
|
| 572 |
-
<bold>false</bold>
|
| 573 |
-
</font>
|
| 574 |
-
</property>
|
| 575 |
-
<property name="styleSheet">
|
| 576 |
-
<string notr="true"/>
|
| 577 |
-
</property>
|
| 578 |
-
<property name="title">
|
| 579 |
-
<string>Step 3: Select and Run Jobs</string>
|
| 580 |
-
</property>
|
| 581 |
-
<layout class="QGridLayout" name="gridLayout_8">
|
| 582 |
-
<property name="topMargin">
|
| 583 |
-
<number>15</number>
|
| 584 |
-
</property>
|
| 585 |
-
<item row="0" column="0">
|
| 586 |
-
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
| 587 |
-
<item>
|
| 588 |
-
<layout class="QHBoxLayout" name="horizontalLayout_8">
|
| 589 |
-
<item>
|
| 590 |
-
<widget class="QPushButton" name="pbtnRemoveJob">
|
| 591 |
-
<property name="sizePolicy">
|
| 592 |
-
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 593 |
-
<horstretch>0</horstretch>
|
| 594 |
-
<verstretch>0</verstretch>
|
| 595 |
-
</sizepolicy>
|
| 596 |
-
</property>
|
| 597 |
-
<property name="minimumSize">
|
| 598 |
-
<size>
|
| 599 |
-
<width>0</width>
|
| 600 |
-
<height>0</height>
|
| 601 |
-
</size>
|
| 602 |
-
</property>
|
| 603 |
-
<property name="text">
|
| 604 |
-
<string>Remove Job</string>
|
| 605 |
-
</property>
|
| 606 |
-
</widget>
|
| 607 |
-
</item>
|
| 608 |
-
<item>
|
| 609 |
-
<widget class="QPushButton" name="pbtnRemoveAllJobs">
|
| 610 |
-
<property name="sizePolicy">
|
| 611 |
-
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 612 |
-
<horstretch>0</horstretch>
|
| 613 |
-
<verstretch>0</verstretch>
|
| 614 |
-
</sizepolicy>
|
| 615 |
-
</property>
|
| 616 |
-
<property name="minimumSize">
|
| 617 |
-
<size>
|
| 618 |
-
<width>0</width>
|
| 619 |
-
<height>0</height>
|
| 620 |
-
</size>
|
| 621 |
-
</property>
|
| 622 |
-
<property name="text">
|
| 623 |
-
<string>Remove All Jobs</string>
|
| 624 |
-
</property>
|
| 625 |
-
</widget>
|
| 626 |
-
</item>
|
| 627 |
-
<item>
|
| 628 |
-
<widget class="QPushButton" name="pbtnRunAllJobs">
|
| 629 |
-
<property name="sizePolicy">
|
| 630 |
-
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 631 |
-
<horstretch>0</horstretch>
|
| 632 |
-
<verstretch>0</verstretch>
|
| 633 |
-
</sizepolicy>
|
| 634 |
-
</property>
|
| 635 |
-
<property name="minimumSize">
|
| 636 |
-
<size>
|
| 637 |
-
<width>0</width>
|
| 638 |
-
<height>0</height>
|
| 639 |
-
</size>
|
| 640 |
-
</property>
|
| 641 |
-
<property name="text">
|
| 642 |
-
<string>Run All Jobs</string>
|
| 643 |
-
</property>
|
| 644 |
-
</widget>
|
| 645 |
-
</item>
|
| 646 |
-
</layout>
|
| 647 |
-
</item>
|
| 648 |
-
<item>
|
| 649 |
-
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
| 650 |
-
<item>
|
| 651 |
-
<widget class="QLabel" name="lblProgressOutput">
|
| 652 |
-
<property name="sizePolicy">
|
| 653 |
-
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 654 |
-
<horstretch>0</horstretch>
|
| 655 |
-
<verstretch>0</verstretch>
|
| 656 |
-
</sizepolicy>
|
| 657 |
-
</property>
|
| 658 |
-
<property name="text">
|
| 659 |
-
<string>Progress Output</string>
|
| 660 |
-
</property>
|
| 661 |
-
<property name="alignment">
|
| 662 |
-
<set>Qt::AlignCenter</set>
|
| 663 |
-
</property>
|
| 664 |
-
</widget>
|
| 665 |
-
</item>
|
| 666 |
-
</layout>
|
| 667 |
-
</item>
|
| 668 |
-
</layout>
|
| 669 |
-
</item>
|
| 670 |
-
<item row="1" column="0">
|
| 671 |
-
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
| 672 |
-
<item>
|
| 673 |
-
<layout class="QVBoxLayout" name="verticalLayout_3">
|
| 674 |
-
<item>
|
| 675 |
-
<widget class="QTableWidget" name="tableJobs">
|
| 676 |
-
<property name="sizePolicy">
|
| 677 |
-
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
| 678 |
-
<horstretch>0</horstretch>
|
| 679 |
-
<verstretch>0</verstretch>
|
| 680 |
-
</sizepolicy>
|
| 681 |
-
</property>
|
| 682 |
-
<property name="minimumSize">
|
| 683 |
-
<size>
|
| 684 |
-
<width>0</width>
|
| 685 |
-
<height>0</height>
|
| 686 |
-
</size>
|
| 687 |
-
</property>
|
| 688 |
-
<property name="maximumSize">
|
| 689 |
-
<size>
|
| 690 |
-
<width>16777215</width>
|
| 691 |
-
<height>16777215</height>
|
| 692 |
-
</size>
|
| 693 |
-
</property>
|
| 694 |
-
<attribute name="horizontalHeaderDefaultSectionSize">
|
| 695 |
-
<number>165</number>
|
| 696 |
-
</attribute>
|
| 697 |
-
<attribute name="horizontalHeaderMinimumSectionSize">
|
| 698 |
-
<number>50</number>
|
| 699 |
-
</attribute>
|
| 700 |
-
<column>
|
| 701 |
-
<property name="text">
|
| 702 |
-
<string>Job Name</string>
|
| 703 |
-
</property>
|
| 704 |
-
</column>
|
| 705 |
-
<column>
|
| 706 |
-
<property name="text">
|
| 707 |
-
<string>Job Status</string>
|
| 708 |
-
</property>
|
| 709 |
-
</column>
|
| 710 |
-
</widget>
|
| 711 |
-
</item>
|
| 712 |
-
</layout>
|
| 713 |
-
</item>
|
| 714 |
-
<item>
|
| 715 |
-
<layout class="QVBoxLayout" name="verticalLayout">
|
| 716 |
-
<item>
|
| 717 |
-
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
| 718 |
-
<property name="sizeConstraint">
|
| 719 |
-
<enum>QLayout::SetDefaultConstraint</enum>
|
| 720 |
-
</property>
|
| 721 |
-
<item>
|
| 722 |
-
<widget class="QTextBrowser" name="txtbrowserJobsProgressOutput">
|
| 723 |
-
<property name="sizePolicy">
|
| 724 |
-
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
| 725 |
-
<horstretch>0</horstretch>
|
| 726 |
-
<verstretch>0</verstretch>
|
| 727 |
-
</sizepolicy>
|
| 728 |
-
</property>
|
| 729 |
-
<property name="minimumSize">
|
| 730 |
-
<size>
|
| 731 |
-
<width>0</width>
|
| 732 |
-
<height>0</height>
|
| 733 |
-
</size>
|
| 734 |
-
</property>
|
| 735 |
-
<property name="verticalScrollBarPolicy">
|
| 736 |
-
<enum>Qt::ScrollBarAsNeeded</enum>
|
| 737 |
-
</property>
|
| 738 |
-
<property name="horizontalScrollBarPolicy">
|
| 739 |
-
<enum>Qt::ScrollBarAsNeeded</enum>
|
| 740 |
-
</property>
|
| 741 |
-
<property name="sizeAdjustPolicy">
|
| 742 |
-
<enum>QAbstractScrollArea::AdjustIgnored</enum>
|
| 743 |
-
</property>
|
| 744 |
-
<property name="lineWrapMode">
|
| 745 |
-
<enum>QTextEdit::NoWrap</enum>
|
| 746 |
-
</property>
|
| 747 |
-
</widget>
|
| 748 |
-
</item>
|
| 749 |
-
</layout>
|
| 750 |
-
</item>
|
| 751 |
-
<item>
|
| 752 |
-
<widget class="QProgressBar" name="progbarJobs">
|
| 753 |
-
<property name="minimumSize">
|
| 754 |
-
<size>
|
| 755 |
-
<width>0</width>
|
| 756 |
-
<height>0</height>
|
| 757 |
-
</size>
|
| 758 |
-
</property>
|
| 759 |
-
<property name="value">
|
| 760 |
-
<number>0</number>
|
| 761 |
-
</property>
|
| 762 |
-
</widget>
|
| 763 |
-
</item>
|
| 764 |
-
</layout>
|
| 765 |
-
</item>
|
| 766 |
-
</layout>
|
| 767 |
-
</item>
|
| 768 |
-
</layout>
|
| 769 |
-
</widget>
|
| 770 |
-
</item>
|
| 771 |
-
<item row="0" column="2" alignment="Qt::AlignRight">
|
| 772 |
-
<widget class="QLabel" name="lblRequired">
|
| 773 |
-
<property name="maximumSize">
|
| 774 |
-
<size>
|
| 775 |
-
<width>16777215</width>
|
| 776 |
-
<height>33</height>
|
| 777 |
-
</size>
|
| 778 |
-
</property>
|
| 779 |
-
<property name="text">
|
| 780 |
-
<string><html><head/><body><p><span style=" color:#fc0107;">* Required</span></p></body></html></string>
|
| 781 |
-
</property>
|
| 782 |
-
</widget>
|
| 783 |
-
</item>
|
| 784 |
-
</layout>
|
| 785 |
-
</widget>
|
| 786 |
-
<action name="visit_repo">
|
| 787 |
-
<property name="text">
|
| 788 |
-
<string>Visit Repository</string>
|
| 789 |
-
</property>
|
| 790 |
-
</action>
|
| 791 |
-
<action name="go_ncbi">
|
| 792 |
-
<property name="text">
|
| 793 |
-
<string>Go to NCBI</string>
|
| 794 |
-
</property>
|
| 795 |
-
</action>
|
| 796 |
-
<action name="actionUpload_New_Endonuclease">
|
| 797 |
-
<property name="text">
|
| 798 |
-
<string>Define New Endonuclease</string>
|
| 799 |
-
</property>
|
| 800 |
-
</action>
|
| 801 |
-
</widget>
|
| 802 |
-
<tabstops>
|
| 803 |
-
<tabstop>ledOrganismName</tabstop>
|
| 804 |
-
<tabstop>ledStrain</tabstop>
|
| 805 |
-
<tabstop>cmbEndonuclease</tabstop>
|
| 806 |
-
<tabstop>ledOrganismCode</tabstop>
|
| 807 |
-
<tabstop>ledSeedLength</tabstop>
|
| 808 |
-
<tabstop>ledFiveLength</tabstop>
|
| 809 |
-
<tabstop>ledThreeLength</tabstop>
|
| 810 |
-
<tabstop>chckGenerateRepeats</tabstop>
|
| 811 |
-
<tabstop>chckMultithreading</tabstop>
|
| 812 |
-
<tabstop>pbtnNCBISearch</tabstop>
|
| 813 |
-
<tabstop>pbtnBrowseFile</tabstop>
|
| 814 |
-
<tabstop>ledSelectedFile</tabstop>
|
| 815 |
-
<tabstop>pbtnRemoveJob</tabstop>
|
| 816 |
-
<tabstop>pbtnRemoveAllJobs</tabstop>
|
| 817 |
-
<tabstop>pbtnRunAllJobs</tabstop>
|
| 818 |
-
<tabstop>tableJobs</tabstop>
|
| 819 |
-
<tabstop>txtbrowserJobsProgressOutput</tabstop>
|
| 820 |
-
</tabstops>
|
| 821 |
-
<resources/>
|
| 822 |
-
<connections/>
|
| 823 |
-
</ui>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -6,7 +6,7 @@
|
|
| 6 |
<rect>
|
| 7 |
<x>0</x>
|
| 8 |
<y>0</y>
|
| 9 |
-
<width>
|
| 10 |
<height>656</height>
|
| 11 |
</rect>
|
| 12 |
</property>
|
|
@@ -20,97 +20,67 @@
|
|
| 20 |
</property>
|
| 21 |
<widget class="QWidget" name="centralwidget">
|
| 22 |
<layout class="QGridLayout" name="gridLayout_2">
|
| 23 |
-
<item row="
|
| 24 |
<layout class="QGridLayout" name="gridLayout">
|
| 25 |
<item row="2" column="0" colspan="2">
|
| 26 |
-
<widget class="QGroupBox" name="
|
| 27 |
<property name="title">
|
| 28 |
-
<string>Step
|
| 29 |
</property>
|
| 30 |
-
<layout class="QGridLayout" name="
|
| 31 |
-
<item row="2" column="
|
| 32 |
-
<widget class="
|
| 33 |
-
<property name="
|
| 34 |
-
<
|
| 35 |
-
<horstretch>0</horstretch>
|
| 36 |
-
<verstretch>0</verstretch>
|
| 37 |
-
</sizepolicy>
|
| 38 |
</property>
|
| 39 |
</widget>
|
| 40 |
</item>
|
| 41 |
-
<item row="
|
| 42 |
-
<widget class="
|
| 43 |
-
<property name="toolTip">
|
| 44 |
-
<string><html><head/><body><p><span style=" font-size:12pt;">This is the reference organism that off-target will be ran against. List is populated based on currenet CSPR files in CASPER database directory.</span></p></body></html></string>
|
| 45 |
-
</property>
|
| 46 |
<property name="text">
|
| 47 |
-
<string>
|
| 48 |
</property>
|
| 49 |
</widget>
|
| 50 |
</item>
|
| 51 |
-
<item row="
|
| 52 |
-
<widget class="
|
| 53 |
-
<property name="
|
| 54 |
-
<
|
| 55 |
-
<horstretch>0</horstretch>
|
| 56 |
-
<verstretch>0</verstretch>
|
| 57 |
-
</sizepolicy>
|
| 58 |
</property>
|
| 59 |
</widget>
|
| 60 |
</item>
|
| 61 |
-
<item row="
|
| 62 |
-
<
|
| 63 |
-
<property name="
|
| 64 |
-
<
|
| 65 |
-
</property>
|
| 66 |
-
<property name="sizeType">
|
| 67 |
-
<enum>QSizePolicy::Preferred</enum>
|
| 68 |
</property>
|
| 69 |
-
<property name="
|
| 70 |
-
<
|
| 71 |
-
<width>20</width>
|
| 72 |
-
<height>10</height>
|
| 73 |
-
</size>
|
| 74 |
</property>
|
| 75 |
-
</
|
| 76 |
</item>
|
| 77 |
-
<item row="
|
| 78 |
-
<widget class="QLabel" name="
|
| 79 |
<property name="toolTip">
|
| 80 |
-
<string><html><head/><body><p>
|
| 81 |
</property>
|
| 82 |
<property name="text">
|
| 83 |
-
<string>
|
| 84 |
</property>
|
| 85 |
</widget>
|
| 86 |
</item>
|
| 87 |
-
<item row="
|
| 88 |
-
<
|
| 89 |
-
<property name="
|
| 90 |
-
<
|
| 91 |
-
</property>
|
| 92 |
-
<property name="sizeType">
|
| 93 |
-
<enum>QSizePolicy::Preferred</enum>
|
| 94 |
</property>
|
| 95 |
-
<property name="
|
| 96 |
-
<
|
| 97 |
-
<width>20</width>
|
| 98 |
-
<height>10</height>
|
| 99 |
-
</size>
|
| 100 |
</property>
|
| 101 |
-
</
|
| 102 |
</item>
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
</item>
|
| 106 |
-
<item row="3" column="0" colspan="2">
|
| 107 |
-
<widget class="QGroupBox" name="Step2">
|
| 108 |
-
<property name="title">
|
| 109 |
-
<string>Step 2: Set Parameters</string>
|
| 110 |
-
</property>
|
| 111 |
-
<layout class="QGridLayout" name="gridLayout_5">
|
| 112 |
-
<item row="1" column="2">
|
| 113 |
-
<widget class="QDoubleSpinBox" name="toleranceSpinBox">
|
| 114 |
<property name="maximum">
|
| 115 |
<double>0.500000000000000</double>
|
| 116 |
</property>
|
|
@@ -122,104 +92,51 @@
|
|
| 122 |
</property>
|
| 123 |
</widget>
|
| 124 |
</item>
|
| 125 |
-
<item row="
|
| 126 |
-
<widget class="
|
| 127 |
-
</item>
|
| 128 |
-
<item row="5" column="0">
|
| 129 |
-
<widget class="QLabel" name="label">
|
| 130 |
<property name="text">
|
| 131 |
-
<string
|
| 132 |
-
</property>
|
| 133 |
-
</widget>
|
| 134 |
-
</item>
|
| 135 |
-
<item row="0" column="0" colspan="3">
|
| 136 |
-
<spacer name="verticalSpacer_4">
|
| 137 |
-
<property name="orientation">
|
| 138 |
-
<enum>Qt::Vertical</enum>
|
| 139 |
-
</property>
|
| 140 |
-
<property name="sizeType">
|
| 141 |
-
<enum>QSizePolicy::Preferred</enum>
|
| 142 |
-
</property>
|
| 143 |
-
<property name="sizeHint" stdset="0">
|
| 144 |
-
<size>
|
| 145 |
-
<width>20</width>
|
| 146 |
-
<height>10</height>
|
| 147 |
-
</size>
|
| 148 |
-
</property>
|
| 149 |
-
</spacer>
|
| 150 |
-
</item>
|
| 151 |
-
<item row="6" column="0" colspan="3">
|
| 152 |
-
<spacer name="verticalSpacer_6">
|
| 153 |
-
<property name="orientation">
|
| 154 |
-
<enum>Qt::Vertical</enum>
|
| 155 |
-
</property>
|
| 156 |
-
<property name="sizeType">
|
| 157 |
-
<enum>QSizePolicy::Preferred</enum>
|
| 158 |
-
</property>
|
| 159 |
-
<property name="sizeHint" stdset="0">
|
| 160 |
-
<size>
|
| 161 |
-
<width>20</width>
|
| 162 |
-
<height>10</height>
|
| 163 |
-
</size>
|
| 164 |
-
</property>
|
| 165 |
-
</spacer>
|
| 166 |
-
</item>
|
| 167 |
-
<item row="1" column="0">
|
| 168 |
-
<widget class="QLabel" name="label_4">
|
| 169 |
-
<property name="toolTip">
|
| 170 |
-
<string><html><head/><body><p>Potential off-target sites scoring lower than this value are dismissed (I.e. they do not pose a threat for off-target activity). Computational time increases as tolerance value decreases.</p></body></html></string>
|
| 171 |
</property>
|
| 172 |
-
<property name="
|
| 173 |
-
<string>
|
| 174 |
</property>
|
| 175 |
</widget>
|
| 176 |
</item>
|
| 177 |
-
<item row="
|
| 178 |
-
<widget class="
|
| 179 |
-
<property name="toolTip">
|
| 180 |
-
<string><html><head/><body><p>Check this option if you only want to see the average off-target score for each gRNA. Leaving this option unchecked will report the average off-target score as well as the putatitve off-target sequences, their locations, and individual off-target scores.</p></body></html></string>
|
| 181 |
-
</property>
|
| 182 |
-
<property name="text">
|
| 183 |
-
<string>Average Output</string>
|
| 184 |
-
</property>
|
| 185 |
-
</widget>
|
| 186 |
</item>
|
| 187 |
-
<item row="
|
| 188 |
-
<widget class="
|
| 189 |
<property name="text">
|
| 190 |
-
<string
|
| 191 |
</property>
|
| 192 |
</widget>
|
| 193 |
</item>
|
| 194 |
-
<item row="
|
| 195 |
-
<widget class="
|
| 196 |
-
<property name="toolTip">
|
| 197 |
-
<string><html><head/><body><p>If the number of mismatches in a putative off-target site exceeds this number, CASPER will not score the site. Computational time increases as this number increases.</p></body></html></string>
|
| 198 |
-
</property>
|
| 199 |
-
<property name="text">
|
| 200 |
-
<string>Max No. Mismatches:</string>
|
| 201 |
-
</property>
|
| 202 |
-
</widget>
|
| 203 |
-
</item>
|
| 204 |
-
<item row="5" column="1">
|
| 205 |
-
<widget class="QCheckBox" name="outputCheckBox">
|
| 206 |
<property name="text">
|
| 207 |
<string>No</string>
|
| 208 |
</property>
|
| 209 |
</widget>
|
| 210 |
</item>
|
| 211 |
-
<item row="5" column="2">
|
| 212 |
-
<widget class="QLineEdit" name="outputFileName">
|
| 213 |
-
<property name="placeholderText">
|
| 214 |
-
<string>Yes. Name the file.</string>
|
| 215 |
-
</property>
|
| 216 |
-
</widget>
|
| 217 |
-
</item>
|
| 218 |
</layout>
|
| 219 |
</widget>
|
| 220 |
</item>
|
| 221 |
-
<item row="
|
| 222 |
-
<widget class="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 223 |
<property name="minimumSize">
|
| 224 |
<size>
|
| 225 |
<width>125</width>
|
|
@@ -227,87 +144,77 @@
|
|
| 227 |
</size>
|
| 228 |
</property>
|
| 229 |
<property name="text">
|
| 230 |
-
<string>
|
| 231 |
</property>
|
| 232 |
</widget>
|
| 233 |
</item>
|
| 234 |
<item row="1" column="0" colspan="2">
|
| 235 |
-
<widget class="
|
| 236 |
-
<property name="orientation">
|
| 237 |
-
<enum>Qt::Horizontal</enum>
|
| 238 |
-
</property>
|
| 239 |
-
</widget>
|
| 240 |
-
</item>
|
| 241 |
-
<item row="4" column="0" colspan="2">
|
| 242 |
-
<widget class="QGroupBox" name="Step3">
|
| 243 |
<property name="title">
|
| 244 |
-
<string>Step
|
| 245 |
</property>
|
| 246 |
-
<layout class="QGridLayout" name="
|
| 247 |
-
<item row="
|
| 248 |
-
<widget class="
|
|
|
|
|
|
|
|
|
|
| 249 |
<property name="text">
|
| 250 |
-
<string>
|
| 251 |
</property>
|
| 252 |
</widget>
|
| 253 |
</item>
|
| 254 |
-
<item row="
|
| 255 |
-
<widget class="
|
| 256 |
-
<property name="
|
| 257 |
-
<
|
|
|
|
|
|
|
|
|
|
| 258 |
</property>
|
| 259 |
</widget>
|
| 260 |
</item>
|
| 261 |
-
<item row="
|
| 262 |
-
<
|
| 263 |
-
<property name="
|
| 264 |
-
<
|
| 265 |
-
</property>
|
| 266 |
-
<property name="sizeType">
|
| 267 |
-
<enum>QSizePolicy::Preferred</enum>
|
| 268 |
</property>
|
| 269 |
-
<property name="
|
| 270 |
-
<
|
| 271 |
-
<width>20</width>
|
| 272 |
-
<height>10</height>
|
| 273 |
-
</size>
|
| 274 |
</property>
|
| 275 |
-
</
|
| 276 |
</item>
|
| 277 |
-
<item row="
|
| 278 |
-
<
|
| 279 |
-
<property name="
|
| 280 |
-
<
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
</property>
|
| 285 |
-
<property name="sizeHint" stdset="0">
|
| 286 |
-
<size>
|
| 287 |
-
<width>20</width>
|
| 288 |
-
<height>10</height>
|
| 289 |
-
</size>
|
| 290 |
</property>
|
| 291 |
-
</
|
| 292 |
</item>
|
| 293 |
</layout>
|
| 294 |
</widget>
|
| 295 |
</item>
|
| 296 |
-
<item row="
|
| 297 |
-
<widget class="
|
| 298 |
-
<property name="
|
| 299 |
-
<
|
| 300 |
-
<horstretch>0</horstretch>
|
| 301 |
-
<verstretch>0</verstretch>
|
| 302 |
-
</sizepolicy>
|
| 303 |
-
</property>
|
| 304 |
-
<property name="text">
|
| 305 |
-
<string>Off-Target Analysis</string>
|
| 306 |
</property>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 307 |
</widget>
|
| 308 |
</item>
|
| 309 |
-
<item row="
|
| 310 |
-
<widget class="QPushButton" name="
|
| 311 |
<property name="minimumSize">
|
| 312 |
<size>
|
| 313 |
<width>125</width>
|
|
@@ -315,79 +222,14 @@
|
|
| 315 |
</size>
|
| 316 |
</property>
|
| 317 |
<property name="text">
|
| 318 |
-
<string>
|
| 319 |
</property>
|
| 320 |
</widget>
|
| 321 |
</item>
|
| 322 |
</layout>
|
| 323 |
</item>
|
| 324 |
-
<item row="2" column="1">
|
| 325 |
-
<spacer name="verticalSpacer">
|
| 326 |
-
<property name="orientation">
|
| 327 |
-
<enum>Qt::Vertical</enum>
|
| 328 |
-
</property>
|
| 329 |
-
<property name="sizeType">
|
| 330 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 331 |
-
</property>
|
| 332 |
-
<property name="sizeHint" stdset="0">
|
| 333 |
-
<size>
|
| 334 |
-
<width>20</width>
|
| 335 |
-
<height>20</height>
|
| 336 |
-
</size>
|
| 337 |
-
</property>
|
| 338 |
-
</spacer>
|
| 339 |
-
</item>
|
| 340 |
-
<item row="1" column="2">
|
| 341 |
-
<spacer name="horizontalSpacer_2">
|
| 342 |
-
<property name="orientation">
|
| 343 |
-
<enum>Qt::Horizontal</enum>
|
| 344 |
-
</property>
|
| 345 |
-
<property name="sizeType">
|
| 346 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 347 |
-
</property>
|
| 348 |
-
<property name="sizeHint" stdset="0">
|
| 349 |
-
<size>
|
| 350 |
-
<width>20</width>
|
| 351 |
-
<height>20</height>
|
| 352 |
-
</size>
|
| 353 |
-
</property>
|
| 354 |
-
</spacer>
|
| 355 |
-
</item>
|
| 356 |
-
<item row="1" column="0">
|
| 357 |
-
<spacer name="horizontalSpacer">
|
| 358 |
-
<property name="orientation">
|
| 359 |
-
<enum>Qt::Horizontal</enum>
|
| 360 |
-
</property>
|
| 361 |
-
<property name="sizeType">
|
| 362 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 363 |
-
</property>
|
| 364 |
-
<property name="sizeHint" stdset="0">
|
| 365 |
-
<size>
|
| 366 |
-
<width>20</width>
|
| 367 |
-
<height>20</height>
|
| 368 |
-
</size>
|
| 369 |
-
</property>
|
| 370 |
-
</spacer>
|
| 371 |
-
</item>
|
| 372 |
-
<item row="0" column="1">
|
| 373 |
-
<spacer name="verticalSpacer_2">
|
| 374 |
-
<property name="orientation">
|
| 375 |
-
<enum>Qt::Vertical</enum>
|
| 376 |
-
</property>
|
| 377 |
-
<property name="sizeType">
|
| 378 |
-
<enum>QSizePolicy::Fixed</enum>
|
| 379 |
-
</property>
|
| 380 |
-
<property name="sizeHint" stdset="0">
|
| 381 |
-
<size>
|
| 382 |
-
<width>20</width>
|
| 383 |
-
<height>20</height>
|
| 384 |
-
</size>
|
| 385 |
-
</property>
|
| 386 |
-
</spacer>
|
| 387 |
-
</item>
|
| 388 |
</layout>
|
| 389 |
</widget>
|
| 390 |
-
<widget class="QStatusBar" name="statusbar"/>
|
| 391 |
</widget>
|
| 392 |
<resources/>
|
| 393 |
<connections/>
|
|
|
|
| 6 |
<rect>
|
| 7 |
<x>0</x>
|
| 8 |
<y>0</y>
|
| 9 |
+
<width>524</width>
|
| 10 |
<height>656</height>
|
| 11 |
</rect>
|
| 12 |
</property>
|
|
|
|
| 20 |
</property>
|
| 21 |
<widget class="QWidget" name="centralwidget">
|
| 22 |
<layout class="QGridLayout" name="gridLayout_2">
|
| 23 |
+
<item row="0" column="0">
|
| 24 |
<layout class="QGridLayout" name="gridLayout">
|
| 25 |
<item row="2" column="0" colspan="2">
|
| 26 |
+
<widget class="QGroupBox" name="grpStep2">
|
| 27 |
<property name="title">
|
| 28 |
+
<string>Step 2: Set Parameters</string>
|
| 29 |
</property>
|
| 30 |
+
<layout class="QGridLayout" name="gridLayout_5">
|
| 31 |
+
<item row="2" column="3">
|
| 32 |
+
<widget class="QRadioButton" name="rbtnAverageOutputNo">
|
| 33 |
+
<property name="text">
|
| 34 |
+
<string>No</string>
|
|
|
|
|
|
|
|
|
|
| 35 |
</property>
|
| 36 |
</widget>
|
| 37 |
</item>
|
| 38 |
+
<item row="2" column="2">
|
| 39 |
+
<widget class="QRadioButton" name="rbtnAverageOutputYes">
|
|
|
|
|
|
|
|
|
|
| 40 |
<property name="text">
|
| 41 |
+
<string>Yes</string>
|
| 42 |
</property>
|
| 43 |
</widget>
|
| 44 |
</item>
|
| 45 |
+
<item row="4" column="0">
|
| 46 |
+
<widget class="QLabel" name="lblSaveOutputFile">
|
| 47 |
+
<property name="text">
|
| 48 |
+
<string>Save Output File?</string>
|
|
|
|
|
|
|
|
|
|
| 49 |
</property>
|
| 50 |
</widget>
|
| 51 |
</item>
|
| 52 |
+
<item row="1" column="0">
|
| 53 |
+
<widget class="QLabel" name="lblMaxNoMismatches">
|
| 54 |
+
<property name="toolTip">
|
| 55 |
+
<string><html><head/><body><p>If the number of mismatches in a putative off-target site exceeds this number, CASPER will not score the site. Computational time increases as this number increases.</p></body></html></string>
|
|
|
|
|
|
|
|
|
|
| 56 |
</property>
|
| 57 |
+
<property name="text">
|
| 58 |
+
<string>Max No. Mismatches:</string>
|
|
|
|
|
|
|
|
|
|
| 59 |
</property>
|
| 60 |
+
</widget>
|
| 61 |
</item>
|
| 62 |
+
<item row="0" column="0">
|
| 63 |
+
<widget class="QLabel" name="lblTolerance">
|
| 64 |
<property name="toolTip">
|
| 65 |
+
<string><html><head/><body><p>Potential off-target sites scoring lower than this value are dismissed (I.e. they do not pose a threat for off-target activity). Computational time increases as tolerance value decreases.</p></body></html></string>
|
| 66 |
</property>
|
| 67 |
<property name="text">
|
| 68 |
+
<string>Tolerance:</string>
|
| 69 |
</property>
|
| 70 |
</widget>
|
| 71 |
</item>
|
| 72 |
+
<item row="2" column="0">
|
| 73 |
+
<widget class="QLabel" name="lblAverageOutput">
|
| 74 |
+
<property name="toolTip">
|
| 75 |
+
<string><html><head/><body><p>Check this option if you only want to see the average off-target score for each gRNA. Leaving this option unchecked will report the average off-target score as well as the putatitve off-target sequences, their locations, and individual off-target scores.</p></body></html></string>
|
|
|
|
|
|
|
|
|
|
| 76 |
</property>
|
| 77 |
+
<property name="text">
|
| 78 |
+
<string>Average Output</string>
|
|
|
|
|
|
|
|
|
|
| 79 |
</property>
|
| 80 |
+
</widget>
|
| 81 |
</item>
|
| 82 |
+
<item row="0" column="2" colspan="2">
|
| 83 |
+
<widget class="QDoubleSpinBox" name="dspnTolerance">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
<property name="maximum">
|
| 85 |
<double>0.500000000000000</double>
|
| 86 |
</property>
|
|
|
|
| 92 |
</property>
|
| 93 |
</widget>
|
| 94 |
</item>
|
| 95 |
+
<item row="5" column="2" colspan="2">
|
| 96 |
+
<widget class="QLineEdit" name="ledSaveOutputFile">
|
|
|
|
|
|
|
|
|
|
| 97 |
<property name="text">
|
| 98 |
+
<string/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
</property>
|
| 100 |
+
<property name="placeholderText">
|
| 101 |
+
<string>Please input the file name</string>
|
| 102 |
</property>
|
| 103 |
</widget>
|
| 104 |
</item>
|
| 105 |
+
<item row="1" column="2" colspan="2">
|
| 106 |
+
<widget class="QComboBox" name="cmbMaxNoMismatches"/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
</item>
|
| 108 |
+
<item row="4" column="2">
|
| 109 |
+
<widget class="QRadioButton" name="rbtnSaveOutputFileYes">
|
| 110 |
<property name="text">
|
| 111 |
+
<string>Yes</string>
|
| 112 |
</property>
|
| 113 |
</widget>
|
| 114 |
</item>
|
| 115 |
+
<item row="4" column="3">
|
| 116 |
+
<widget class="QRadioButton" name="rbtnSaveOutputFileNo">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
<property name="text">
|
| 118 |
<string>No</string>
|
| 119 |
</property>
|
| 120 |
</widget>
|
| 121 |
</item>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
</layout>
|
| 123 |
</widget>
|
| 124 |
</item>
|
| 125 |
+
<item row="0" column="0" colspan="2">
|
| 126 |
+
<widget class="QLabel" name="lblTitle">
|
| 127 |
+
<property name="sizePolicy">
|
| 128 |
+
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 129 |
+
<horstretch>0</horstretch>
|
| 130 |
+
<verstretch>0</verstretch>
|
| 131 |
+
</sizepolicy>
|
| 132 |
+
</property>
|
| 133 |
+
<property name="text">
|
| 134 |
+
<string>Off-Target Analysis</string>
|
| 135 |
+
</property>
|
| 136 |
+
</widget>
|
| 137 |
+
</item>
|
| 138 |
+
<item row="4" column="0" alignment="Qt::AlignLeft">
|
| 139 |
+
<widget class="QPushButton" name="pbtnCancel">
|
| 140 |
<property name="minimumSize">
|
| 141 |
<size>
|
| 142 |
<width>125</width>
|
|
|
|
| 144 |
</size>
|
| 145 |
</property>
|
| 146 |
<property name="text">
|
| 147 |
+
<string>Cancel</string>
|
| 148 |
</property>
|
| 149 |
</widget>
|
| 150 |
</item>
|
| 151 |
<item row="1" column="0" colspan="2">
|
| 152 |
+
<widget class="QGroupBox" name="grpStep1">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
<property name="title">
|
| 154 |
+
<string>Step 1: Select References</string>
|
| 155 |
</property>
|
| 156 |
+
<layout class="QGridLayout" name="gridLayout_4">
|
| 157 |
+
<item row="0" column="0">
|
| 158 |
+
<widget class="QLabel" name="lblOrganism">
|
| 159 |
+
<property name="toolTip">
|
| 160 |
+
<string><html><head/><body><p><span style=" font-size:12pt;">This is the reference organism that off-target will be ran against. List is populated based on currenet CSPR files in CASPER database directory.</span></p></body></html></string>
|
| 161 |
+
</property>
|
| 162 |
<property name="text">
|
| 163 |
+
<string>Organism:</string>
|
| 164 |
</property>
|
| 165 |
</widget>
|
| 166 |
</item>
|
| 167 |
+
<item row="0" column="1">
|
| 168 |
+
<widget class="QComboBox" name="cmbOrganism">
|
| 169 |
+
<property name="sizePolicy">
|
| 170 |
+
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 171 |
+
<horstretch>0</horstretch>
|
| 172 |
+
<verstretch>0</verstretch>
|
| 173 |
+
</sizepolicy>
|
| 174 |
</property>
|
| 175 |
</widget>
|
| 176 |
</item>
|
| 177 |
+
<item row="1" column="0">
|
| 178 |
+
<widget class="QLabel" name="lblEndonuclease">
|
| 179 |
+
<property name="toolTip">
|
| 180 |
+
<string><html><head/><body><p><span style=" font-size:12pt;">This is the reference endonuclease that will be used for off-target analysis.</span></p></body></html></string>
|
|
|
|
|
|
|
|
|
|
| 181 |
</property>
|
| 182 |
+
<property name="text">
|
| 183 |
+
<string>Endonuclease:</string>
|
|
|
|
|
|
|
|
|
|
| 184 |
</property>
|
| 185 |
+
</widget>
|
| 186 |
</item>
|
| 187 |
+
<item row="1" column="1">
|
| 188 |
+
<widget class="QComboBox" name="cmbEndonuclease">
|
| 189 |
+
<property name="sizePolicy">
|
| 190 |
+
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 191 |
+
<horstretch>0</horstretch>
|
| 192 |
+
<verstretch>0</verstretch>
|
| 193 |
+
</sizepolicy>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
</property>
|
| 195 |
+
</widget>
|
| 196 |
</item>
|
| 197 |
</layout>
|
| 198 |
</widget>
|
| 199 |
</item>
|
| 200 |
+
<item row="3" column="0" colspan="2">
|
| 201 |
+
<widget class="QGroupBox" name="grpStep3">
|
| 202 |
+
<property name="title">
|
| 203 |
+
<string>Step 3: Run Off-Target</string>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 204 |
</property>
|
| 205 |
+
<layout class="QGridLayout" name="gridLayout_6">
|
| 206 |
+
<item row="1" column="0">
|
| 207 |
+
<widget class="QProgressBar" name="progBar">
|
| 208 |
+
<property name="value">
|
| 209 |
+
<number>24</number>
|
| 210 |
+
</property>
|
| 211 |
+
</widget>
|
| 212 |
+
</item>
|
| 213 |
+
</layout>
|
| 214 |
</widget>
|
| 215 |
</item>
|
| 216 |
+
<item row="4" column="1" alignment="Qt::AlignRight">
|
| 217 |
+
<widget class="QPushButton" name="pbtnSubmit">
|
| 218 |
<property name="minimumSize">
|
| 219 |
<size>
|
| 220 |
<width>125</width>
|
|
|
|
| 222 |
</size>
|
| 223 |
</property>
|
| 224 |
<property name="text">
|
| 225 |
+
<string>Submit</string>
|
| 226 |
</property>
|
| 227 |
</widget>
|
| 228 |
</item>
|
| 229 |
</layout>
|
| 230 |
</item>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 231 |
</layout>
|
| 232 |
</widget>
|
|
|
|
| 233 |
</widget>
|
| 234 |
<resources/>
|
| 235 |
<connections/>
|
|
@@ -143,7 +143,7 @@
|
|
| 143 |
</widget>
|
| 144 |
</item>
|
| 145 |
<item row="3" column="1" alignment="Qt::AlignRight">
|
| 146 |
-
<widget class="QPushButton" name="
|
| 147 |
<property name="minimumSize">
|
| 148 |
<size>
|
| 149 |
<width>175</width>
|
|
@@ -179,38 +179,6 @@
|
|
| 179 |
<string>Select Organism(s) and Endonuclease:</string>
|
| 180 |
</property>
|
| 181 |
<layout class="QGridLayout" name="gridLayout_6">
|
| 182 |
-
<item row="3" column="0" colspan="2">
|
| 183 |
-
<widget class="QTabWidget" name="tabsSharedSeedHeatmap">
|
| 184 |
-
<property name="sizePolicy">
|
| 185 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
| 186 |
-
<horstretch>0</horstretch>
|
| 187 |
-
<verstretch>0</verstretch>
|
| 188 |
-
</sizepolicy>
|
| 189 |
-
</property>
|
| 190 |
-
<property name="minimumSize">
|
| 191 |
-
<size>
|
| 192 |
-
<width>0</width>
|
| 193 |
-
<height>225</height>
|
| 194 |
-
</size>
|
| 195 |
-
</property>
|
| 196 |
-
<property name="toolTip">
|
| 197 |
-
<string><html><head/><body><p>Heatmap of shared repeats between all selected organisms. Diagonals show number of self-contained repeats. Axis labels correspond to the rows of the organism selection table.</p></body></html></string>
|
| 198 |
-
</property>
|
| 199 |
-
<property name="currentIndex">
|
| 200 |
-
<number>0</number>
|
| 201 |
-
</property>
|
| 202 |
-
<widget class="QWidget" name="tabSharedSeedHeatmap">
|
| 203 |
-
<attribute name="title">
|
| 204 |
-
<string>Shared Seed Heatmap</string>
|
| 205 |
-
</attribute>
|
| 206 |
-
<layout class="QGridLayout" name="gridLayout_3">
|
| 207 |
-
<item row="0" column="0">
|
| 208 |
-
<widget class="QWidget" name="heatmapSeed" native="true"/>
|
| 209 |
-
</item>
|
| 210 |
-
</layout>
|
| 211 |
-
</widget>
|
| 212 |
-
</widget>
|
| 213 |
-
</item>
|
| 214 |
<item row="0" column="0">
|
| 215 |
<widget class="QLabel" name="lblEndonuclease">
|
| 216 |
<property name="sizePolicy">
|
|
@@ -240,45 +208,58 @@
|
|
| 240 |
</property>
|
| 241 |
</widget>
|
| 242 |
</item>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
<item row="2" column="0" colspan="2">
|
| 244 |
<widget class="QPushButton" name="pbtnAnalyzeOrganism">
|
| 245 |
-
<property name="font">
|
| 246 |
-
<font>
|
| 247 |
-
<weight>75</weight>
|
| 248 |
-
<bold>true</bold>
|
| 249 |
-
</font>
|
| 250 |
-
</property>
|
| 251 |
<property name="text">
|
| 252 |
<string>Analyze Organism(s)</string>
|
| 253 |
</property>
|
| 254 |
</widget>
|
| 255 |
</item>
|
| 256 |
-
<item row="
|
| 257 |
-
<widget class="
|
| 258 |
<property name="sizePolicy">
|
| 259 |
-
<sizepolicy hsizetype="
|
| 260 |
<horstretch>0</horstretch>
|
| 261 |
<verstretch>0</verstretch>
|
| 262 |
</sizepolicy>
|
| 263 |
</property>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 264 |
</widget>
|
| 265 |
</item>
|
| 266 |
</layout>
|
| 267 |
</widget>
|
| 268 |
</item>
|
| 269 |
-
<item row="3" column="0" alignment="Qt::AlignLeft">
|
| 270 |
-
<widget class="QPushButton" name="pbtnBack">
|
| 271 |
-
<property name="minimumSize">
|
| 272 |
-
<size>
|
| 273 |
-
<width>125</width>
|
| 274 |
-
<height>0</height>
|
| 275 |
-
</size>
|
| 276 |
-
</property>
|
| 277 |
-
<property name="text">
|
| 278 |
-
<string>Back</string>
|
| 279 |
-
</property>
|
| 280 |
-
</widget>
|
| 281 |
-
</item>
|
| 282 |
</layout>
|
| 283 |
</item>
|
| 284 |
</layout>
|
|
|
|
| 143 |
</widget>
|
| 144 |
</item>
|
| 145 |
<item row="3" column="1" alignment="Qt::AlignRight">
|
| 146 |
+
<widget class="QPushButton" name="pbtnExportSelectedgRNAs">
|
| 147 |
<property name="minimumSize">
|
| 148 |
<size>
|
| 149 |
<width>175</width>
|
|
|
|
| 179 |
<string>Select Organism(s) and Endonuclease:</string>
|
| 180 |
</property>
|
| 181 |
<layout class="QGridLayout" name="gridLayout_6">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
<item row="0" column="0">
|
| 183 |
<widget class="QLabel" name="lblEndonuclease">
|
| 184 |
<property name="sizePolicy">
|
|
|
|
| 208 |
</property>
|
| 209 |
</widget>
|
| 210 |
</item>
|
| 211 |
+
<item row="0" column="1">
|
| 212 |
+
<widget class="QComboBox" name="cmbEndonuclease">
|
| 213 |
+
<property name="sizePolicy">
|
| 214 |
+
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 215 |
+
<horstretch>0</horstretch>
|
| 216 |
+
<verstretch>0</verstretch>
|
| 217 |
+
</sizepolicy>
|
| 218 |
+
</property>
|
| 219 |
+
</widget>
|
| 220 |
+
</item>
|
| 221 |
<item row="2" column="0" colspan="2">
|
| 222 |
<widget class="QPushButton" name="pbtnAnalyzeOrganism">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 223 |
<property name="text">
|
| 224 |
<string>Analyze Organism(s)</string>
|
| 225 |
</property>
|
| 226 |
</widget>
|
| 227 |
</item>
|
| 228 |
+
<item row="3" column="0" colspan="2">
|
| 229 |
+
<widget class="QTabWidget" name="tabsSharedSeedHeatmap">
|
| 230 |
<property name="sizePolicy">
|
| 231 |
+
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
| 232 |
<horstretch>0</horstretch>
|
| 233 |
<verstretch>0</verstretch>
|
| 234 |
</sizepolicy>
|
| 235 |
</property>
|
| 236 |
+
<property name="minimumSize">
|
| 237 |
+
<size>
|
| 238 |
+
<width>0</width>
|
| 239 |
+
<height>225</height>
|
| 240 |
+
</size>
|
| 241 |
+
</property>
|
| 242 |
+
<property name="toolTip">
|
| 243 |
+
<string><html><head/><body><p>Heatmap of shared repeats between all selected organisms. Diagonals show number of self-contained repeats. Axis labels correspond to the rows of the organism selection table.</p></body></html></string>
|
| 244 |
+
</property>
|
| 245 |
+
<property name="currentIndex">
|
| 246 |
+
<number>0</number>
|
| 247 |
+
</property>
|
| 248 |
+
<widget class="QWidget" name="tabSharedSeedHeatmap">
|
| 249 |
+
<attribute name="title">
|
| 250 |
+
<string>Shared Seed Heatmap</string>
|
| 251 |
+
</attribute>
|
| 252 |
+
<layout class="QGridLayout" name="gridLayout_3">
|
| 253 |
+
<item row="0" column="0">
|
| 254 |
+
<widget class="QWidget" name="heatmapSeed" native="true"/>
|
| 255 |
+
</item>
|
| 256 |
+
</layout>
|
| 257 |
+
</widget>
|
| 258 |
</widget>
|
| 259 |
</item>
|
| 260 |
</layout>
|
| 261 |
</widget>
|
| 262 |
</item>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 263 |
</layout>
|
| 264 |
</item>
|
| 265 |
</layout>
|
|
@@ -6,7 +6,7 @@
|
|
| 6 |
<rect>
|
| 7 |
<x>0</x>
|
| 8 |
<y>0</y>
|
| 9 |
-
<width>
|
| 10 |
<height>916</height>
|
| 11 |
</rect>
|
| 12 |
</property>
|
|
@@ -22,255 +22,105 @@
|
|
| 22 |
<layout class="QGridLayout" name="gridLayout_2">
|
| 23 |
<item row="0" column="0">
|
| 24 |
<layout class="QGridLayout" name="gridLayout">
|
| 25 |
-
<item row="
|
| 26 |
-
<widget class="QGroupBox" name="
|
| 27 |
-
<property name="title">
|
| 28 |
-
<string>Guide Analysis</string>
|
| 29 |
-
</property>
|
| 30 |
-
<layout class="QGridLayout" name="gridLayout_5">
|
| 31 |
-
<item row="0" column="1">
|
| 32 |
-
<widget class="QPushButton" name="pbtnCoTargeting">
|
| 33 |
-
<property name="sizePolicy">
|
| 34 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 35 |
-
<horstretch>0</horstretch>
|
| 36 |
-
<verstretch>0</verstretch>
|
| 37 |
-
</sizepolicy>
|
| 38 |
-
</property>
|
| 39 |
-
<property name="minimumSize">
|
| 40 |
-
<size>
|
| 41 |
-
<width>125</width>
|
| 42 |
-
<height>0</height>
|
| 43 |
-
</size>
|
| 44 |
-
</property>
|
| 45 |
-
<property name="toolTip">
|
| 46 |
-
<string><html><head/><body><p>Determine guides with synergistic PAMs (Ex. saCas9 and spCas9 compatible guides). <span style=" font-weight:600;">Note:</span> to analyze an organism for co-targeting guides, separate CSPR files must be generated for each additional endonuclease. Co-targeting endonucleases must have the same PAM directionality, same total gRNA length, and overlapping PAM sequences to be compatible.</p></body></html></string>
|
| 47 |
-
</property>
|
| 48 |
-
<property name="text">
|
| 49 |
-
<string>Co-Targeting</string>
|
| 50 |
-
</property>
|
| 51 |
-
</widget>
|
| 52 |
-
</item>
|
| 53 |
-
<item row="0" column="0">
|
| 54 |
-
<widget class="QPushButton" name="pbtnOffTarget">
|
| 55 |
-
<property name="sizePolicy">
|
| 56 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 57 |
-
<horstretch>0</horstretch>
|
| 58 |
-
<verstretch>0</verstretch>
|
| 59 |
-
</sizepolicy>
|
| 60 |
-
</property>
|
| 61 |
-
<property name="minimumSize">
|
| 62 |
-
<size>
|
| 63 |
-
<width>125</width>
|
| 64 |
-
<height>0</height>
|
| 65 |
-
</size>
|
| 66 |
-
</property>
|
| 67 |
-
<property name="toolTip">
|
| 68 |
-
<string><html><head/><body><p><span style=" font-size:12pt;">Perform off-target analysis on the selected guides.</span></p></body></html></string>
|
| 69 |
-
</property>
|
| 70 |
-
<property name="text">
|
| 71 |
-
<string>Off-Target</string>
|
| 72 |
-
</property>
|
| 73 |
-
</widget>
|
| 74 |
-
</item>
|
| 75 |
-
</layout>
|
| 76 |
-
</widget>
|
| 77 |
-
</item>
|
| 78 |
-
<item row="2" column="1" alignment="Qt::AlignRight">
|
| 79 |
-
<widget class="QPushButton" name="pbtnExportgRNA">
|
| 80 |
-
<property name="sizePolicy">
|
| 81 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 82 |
-
<horstretch>0</horstretch>
|
| 83 |
-
<verstretch>0</verstretch>
|
| 84 |
-
</sizepolicy>
|
| 85 |
-
</property>
|
| 86 |
-
<property name="minimumSize">
|
| 87 |
-
<size>
|
| 88 |
-
<width>175</width>
|
| 89 |
-
<height>0</height>
|
| 90 |
-
</size>
|
| 91 |
-
</property>
|
| 92 |
-
<property name="toolTip">
|
| 93 |
-
<string><html><head/><body><p><span style=" font-size:12pt;">Export selected guides to a CSV file.</span></p></body></html></string>
|
| 94 |
-
</property>
|
| 95 |
-
<property name="text">
|
| 96 |
-
<string>Export Selected gRNAs</string>
|
| 97 |
-
</property>
|
| 98 |
-
</widget>
|
| 99 |
-
</item>
|
| 100 |
-
<item row="0" column="0">
|
| 101 |
-
<widget class="QGroupBox" name="grpGuideViewer">
|
| 102 |
<property name="sizePolicy">
|
| 103 |
-
<sizepolicy hsizetype="
|
| 104 |
<horstretch>0</horstretch>
|
| 105 |
<verstretch>0</verstretch>
|
| 106 |
</sizepolicy>
|
| 107 |
</property>
|
| 108 |
-
<property name="minimumSize">
|
| 109 |
-
<size>
|
| 110 |
-
<width>625</width>
|
| 111 |
-
<height>0</height>
|
| 112 |
-
</size>
|
| 113 |
-
</property>
|
| 114 |
<property name="title">
|
| 115 |
-
<string>
|
| 116 |
</property>
|
| 117 |
-
<layout class="QGridLayout" name="
|
| 118 |
-
<item row="
|
| 119 |
-
<widget class="QLabel" name="
|
| 120 |
-
<property name="sizePolicy">
|
| 121 |
-
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
| 122 |
-
<horstretch>0</horstretch>
|
| 123 |
-
<verstretch>0</verstretch>
|
| 124 |
-
</sizepolicy>
|
| 125 |
-
</property>
|
| 126 |
-
<property name="text">
|
| 127 |
-
<string>Gene:</string>
|
| 128 |
-
</property>
|
| 129 |
-
</widget>
|
| 130 |
-
</item>
|
| 131 |
-
<item row="8" column="2">
|
| 132 |
-
<widget class="QPushButton" name="pbtnScoringOptions">
|
| 133 |
<property name="text">
|
| 134 |
-
<string>
|
| 135 |
</property>
|
| 136 |
</widget>
|
| 137 |
</item>
|
| 138 |
-
<item row="
|
| 139 |
-
<
|
| 140 |
-
<item>
|
| 141 |
-
<widget class="QComboBox" name="cmbEndonuclease">
|
| 142 |
-
<property name="sizePolicy">
|
| 143 |
-
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 144 |
-
<horstretch>0</horstretch>
|
| 145 |
-
<verstretch>0</verstretch>
|
| 146 |
-
</sizepolicy>
|
| 147 |
-
</property>
|
| 148 |
-
<property name="minimumSize">
|
| 149 |
-
<size>
|
| 150 |
-
<width>225</width>
|
| 151 |
-
<height>0</height>
|
| 152 |
-
</size>
|
| 153 |
-
</property>
|
| 154 |
-
</widget>
|
| 155 |
-
</item>
|
| 156 |
-
</layout>
|
| 157 |
</item>
|
| 158 |
-
<item row="
|
| 159 |
-
<widget class="
|
| 160 |
-
<property name="sizePolicy">
|
| 161 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 162 |
-
<horstretch>0</horstretch>
|
| 163 |
-
<verstretch>0</verstretch>
|
| 164 |
-
</sizepolicy>
|
| 165 |
-
</property>
|
| 166 |
-
<property name="minimumSize">
|
| 167 |
-
<size>
|
| 168 |
-
<width>125</width>
|
| 169 |
-
<height>0</height>
|
| 170 |
-
</size>
|
| 171 |
-
</property>
|
| 172 |
-
<property name="toolTip">
|
| 173 |
-
<string><html><head/><body><p><span style=" font-size:12pt;">Additional options for filtering the Guide Viewer Table.</span></p></body></html></string>
|
| 174 |
-
</property>
|
| 175 |
<property name="text">
|
| 176 |
-
<string>
|
| 177 |
</property>
|
| 178 |
</widget>
|
| 179 |
</item>
|
| 180 |
-
<item row="
|
| 181 |
-
<widget class="
|
| 182 |
-
<property name="sizePolicy">
|
| 183 |
-
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
| 184 |
-
<horstretch>0</horstretch>
|
| 185 |
-
<verstretch>0</verstretch>
|
| 186 |
-
</sizepolicy>
|
| 187 |
-
</property>
|
| 188 |
<property name="text">
|
| 189 |
-
<string>
|
| 190 |
</property>
|
| 191 |
</widget>
|
| 192 |
</item>
|
| 193 |
-
<item row="
|
| 194 |
-
<widget class="
|
| 195 |
</item>
|
| 196 |
-
<item row="
|
| 197 |
-
<widget class="
|
| 198 |
-
<property name="
|
| 199 |
-
<
|
| 200 |
-
<horstretch>0</horstretch>
|
| 201 |
-
<verstretch>0</verstretch>
|
| 202 |
-
</sizepolicy>
|
| 203 |
</property>
|
| 204 |
<property name="text">
|
| 205 |
-
<string>
|
| 206 |
</property>
|
| 207 |
</widget>
|
| 208 |
</item>
|
| 209 |
-
<item row="
|
| 210 |
-
<
|
| 211 |
-
<item>
|
| 212 |
-
<widget class="QComboBox" name="cmbGene">
|
| 213 |
-
<property name="sizePolicy">
|
| 214 |
-
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
| 215 |
-
<horstretch>0</horstretch>
|
| 216 |
-
<verstretch>0</verstretch>
|
| 217 |
-
</sizepolicy>
|
| 218 |
-
</property>
|
| 219 |
-
<property name="minimumSize">
|
| 220 |
-
<size>
|
| 221 |
-
<width>225</width>
|
| 222 |
-
<height>0</height>
|
| 223 |
-
</size>
|
| 224 |
-
</property>
|
| 225 |
-
</widget>
|
| 226 |
-
</item>
|
| 227 |
-
</layout>
|
| 228 |
</item>
|
| 229 |
</layout>
|
| 230 |
</widget>
|
| 231 |
</item>
|
| 232 |
-
<item row="0" column="
|
| 233 |
-
<widget class="QGroupBox" name="
|
| 234 |
<property name="sizePolicy">
|
| 235 |
-
<sizepolicy hsizetype="
|
| 236 |
<horstretch>0</horstretch>
|
| 237 |
<verstretch>0</verstretch>
|
| 238 |
</sizepolicy>
|
| 239 |
</property>
|
| 240 |
-
<property name="minimumSize">
|
| 241 |
-
<size>
|
| 242 |
-
<width>0</width>
|
| 243 |
-
<height>0</height>
|
| 244 |
-
</size>
|
| 245 |
-
</property>
|
| 246 |
<property name="title">
|
| 247 |
-
<string>
|
| 248 |
</property>
|
| 249 |
-
<layout class="QGridLayout" name="
|
| 250 |
-
<item row="
|
| 251 |
-
<widget class="
|
| 252 |
-
<property name="
|
| 253 |
-
<
|
| 254 |
-
<
|
| 255 |
-
<
|
| 256 |
-
</
|
| 257 |
</property>
|
| 258 |
</widget>
|
| 259 |
</item>
|
| 260 |
-
<item row="
|
| 261 |
-
<widget class="QPushButton" name="
|
| 262 |
-
<property name="
|
| 263 |
-
<
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
</
|
| 267 |
</property>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 268 |
<property name="minimumSize">
|
| 269 |
<size>
|
| 270 |
-
<width>
|
| 271 |
<height>0</height>
|
| 272 |
</size>
|
| 273 |
</property>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 274 |
<property name="toolTip">
|
| 275 |
<string><html><head/><body><p><span style=" font-size:12pt;">This button clears all highlighted guides from the gene viewer box.</span></p></body></html></string>
|
| 276 |
</property>
|
|
@@ -279,101 +129,81 @@
|
|
| 279 |
</property>
|
| 280 |
</widget>
|
| 281 |
</item>
|
| 282 |
-
<item row="
|
| 283 |
-
<widget class="QLabel" name="
|
| 284 |
-
<property name="sizePolicy">
|
| 285 |
-
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
| 286 |
-
<horstretch>0</horstretch>
|
| 287 |
-
<verstretch>0</verstretch>
|
| 288 |
-
</sizepolicy>
|
| 289 |
-
</property>
|
| 290 |
<property name="text">
|
| 291 |
-
<string>
|
| 292 |
</property>
|
| 293 |
</widget>
|
| 294 |
</item>
|
| 295 |
-
<item row="
|
| 296 |
-
<widget class="
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
<property name="text">
|
| 304 |
-
<string>
|
| 305 |
</property>
|
| 306 |
</widget>
|
| 307 |
</item>
|
| 308 |
-
<item row="
|
| 309 |
-
<widget class="
|
| 310 |
-
<property name="
|
| 311 |
-
<
|
| 312 |
-
<horstretch>0</horstretch>
|
| 313 |
-
<verstretch>0</verstretch>
|
| 314 |
-
</sizepolicy>
|
| 315 |
</property>
|
| 316 |
</widget>
|
| 317 |
</item>
|
| 318 |
-
<item row="
|
| 319 |
-
<widget class="
|
| 320 |
-
<property name="sizePolicy">
|
| 321 |
-
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
| 322 |
-
<horstretch>0</horstretch>
|
| 323 |
-
<verstretch>0</verstretch>
|
| 324 |
-
</sizepolicy>
|
| 325 |
-
</property>
|
| 326 |
-
<property name="minimumSize">
|
| 327 |
-
<size>
|
| 328 |
-
<width>0</width>
|
| 329 |
-
<height>0</height>
|
| 330 |
-
</size>
|
| 331 |
-
</property>
|
| 332 |
-
<property name="toolTip">
|
| 333 |
-
<string><html><head/><body><p>This button will highlight the sequences in the Gene Viewer that match the sequences selected in the table.</p></body></html></string>
|
| 334 |
-
</property>
|
| 335 |
<property name="text">
|
| 336 |
-
<string>
|
| 337 |
</property>
|
| 338 |
</widget>
|
| 339 |
</item>
|
| 340 |
-
<item row="
|
| 341 |
-
<widget class="
|
| 342 |
-
<property name="
|
| 343 |
-
<
|
| 344 |
-
<horstretch>0</horstretch>
|
| 345 |
-
<verstretch>0</verstretch>
|
| 346 |
-
</sizepolicy>
|
| 347 |
</property>
|
| 348 |
</widget>
|
| 349 |
</item>
|
| 350 |
-
<item row="
|
| 351 |
-
<widget class="
|
| 352 |
<property name="text">
|
| 353 |
-
<string>
|
| 354 |
</property>
|
| 355 |
</widget>
|
| 356 |
</item>
|
| 357 |
-
<item row="
|
| 358 |
-
<widget class="QPushButton" name="
|
| 359 |
-
<property name="
|
| 360 |
-
<size>
|
| 361 |
-
<width>0</width>
|
| 362 |
-
<height>0</height>
|
| 363 |
-
</size>
|
| 364 |
</property>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 365 |
<property name="toolTip">
|
| 366 |
-
<string><html><head/><body><p>
|
| 367 |
</property>
|
| 368 |
<property name="text">
|
| 369 |
-
<string>
|
| 370 |
</property>
|
| 371 |
</widget>
|
| 372 |
</item>
|
| 373 |
-
<item row="
|
| 374 |
-
<widget class="QPushButton" name="
|
|
|
|
|
|
|
|
|
|
| 375 |
<property name="text">
|
| 376 |
-
<string>
|
| 377 |
</property>
|
| 378 |
</widget>
|
| 379 |
</item>
|
|
|
|
| 6 |
<rect>
|
| 7 |
<x>0</x>
|
| 8 |
<y>0</y>
|
| 9 |
+
<width>1335</width>
|
| 10 |
<height>916</height>
|
| 11 |
</rect>
|
| 12 |
</property>
|
|
|
|
| 22 |
<layout class="QGridLayout" name="gridLayout_2">
|
| 23 |
<item row="0" column="0">
|
| 24 |
<layout class="QGridLayout" name="gridLayout">
|
| 25 |
+
<item row="0" column="1" rowspan="2">
|
| 26 |
+
<widget class="QGroupBox" name="grpGeneViewer">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
<property name="sizePolicy">
|
| 28 |
+
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
| 29 |
<horstretch>0</horstretch>
|
| 30 |
<verstretch>0</verstretch>
|
| 31 |
</sizepolicy>
|
| 32 |
</property>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
<property name="title">
|
| 34 |
+
<string>Gene Viewer</string>
|
| 35 |
</property>
|
| 36 |
+
<layout class="QGridLayout" name="gridLayout_6">
|
| 37 |
+
<item row="2" column="0">
|
| 38 |
+
<widget class="QLabel" name="lblStartLocation">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
<property name="text">
|
| 40 |
+
<string>Start:</string>
|
| 41 |
</property>
|
| 42 |
</widget>
|
| 43 |
</item>
|
| 44 |
+
<item row="2" column="1">
|
| 45 |
+
<widget class="QLineEdit" name="ledStartLocation"/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
</item>
|
| 47 |
+
<item row="3" column="0">
|
| 48 |
+
<widget class="QLabel" name="lblStopLocation">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
<property name="text">
|
| 50 |
+
<string>Stop:</string>
|
| 51 |
</property>
|
| 52 |
</widget>
|
| 53 |
</item>
|
| 54 |
+
<item row="3" column="2">
|
| 55 |
+
<widget class="QPushButton" name="pbtnResetLocation">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
<property name="text">
|
| 57 |
+
<string>Reset Location</string>
|
| 58 |
</property>
|
| 59 |
</widget>
|
| 60 |
</item>
|
| 61 |
+
<item row="3" column="1">
|
| 62 |
+
<widget class="QLineEdit" name="ledStopLocation"/>
|
| 63 |
</item>
|
| 64 |
+
<item row="2" column="2">
|
| 65 |
+
<widget class="QPushButton" name="pbtnChangeLocation">
|
| 66 |
+
<property name="toolTip">
|
| 67 |
+
<string><html><head/><body><p>This button changes the start and end location of the Gene Viewer sequence, based on what is entered in the Start and Stop boxes.</p></body></html></string>
|
|
|
|
|
|
|
|
|
|
| 68 |
</property>
|
| 69 |
<property name="text">
|
| 70 |
+
<string>Change Location</string>
|
| 71 |
</property>
|
| 72 |
</widget>
|
| 73 |
</item>
|
| 74 |
+
<item row="5" column="0" colspan="5">
|
| 75 |
+
<widget class="QTextEdit" name="txtedGeneViewer"/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
</item>
|
| 77 |
</layout>
|
| 78 |
</widget>
|
| 79 |
</item>
|
| 80 |
+
<item row="0" column="0">
|
| 81 |
+
<widget class="QGroupBox" name="grpGuideViewer">
|
| 82 |
<property name="sizePolicy">
|
| 83 |
+
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
| 84 |
<horstretch>0</horstretch>
|
| 85 |
<verstretch>0</verstretch>
|
| 86 |
</sizepolicy>
|
| 87 |
</property>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
<property name="title">
|
| 89 |
+
<string>Guide Viewer</string>
|
| 90 |
</property>
|
| 91 |
+
<layout class="QGridLayout" name="gridLayout_4">
|
| 92 |
+
<item row="4" column="1" colspan="3">
|
| 93 |
+
<widget class="QComboBox" name="cmbGene">
|
| 94 |
+
<property name="minimumSize">
|
| 95 |
+
<size>
|
| 96 |
+
<width>225</width>
|
| 97 |
+
<height>0</height>
|
| 98 |
+
</size>
|
| 99 |
</property>
|
| 100 |
</widget>
|
| 101 |
</item>
|
| 102 |
+
<item row="10" column="2">
|
| 103 |
+
<widget class="QPushButton" name="pbtnHighlightGuides">
|
| 104 |
+
<property name="toolTip">
|
| 105 |
+
<string><html><head/><body><p>This button will highlight the sequences in the Gene Viewer that match the sequences selected in the table.</p></body></html></string>
|
| 106 |
+
</property>
|
| 107 |
+
<property name="text">
|
| 108 |
+
<string>Highlight Guides</string>
|
| 109 |
</property>
|
| 110 |
+
</widget>
|
| 111 |
+
</item>
|
| 112 |
+
<item row="5" column="1" colspan="3">
|
| 113 |
+
<widget class="QComboBox" name="cmbEndonuclease">
|
| 114 |
<property name="minimumSize">
|
| 115 |
<size>
|
| 116 |
+
<width>225</width>
|
| 117 |
<height>0</height>
|
| 118 |
</size>
|
| 119 |
</property>
|
| 120 |
+
</widget>
|
| 121 |
+
</item>
|
| 122 |
+
<item row="10" column="3">
|
| 123 |
+
<widget class="QPushButton" name="pbtnClearGuides">
|
| 124 |
<property name="toolTip">
|
| 125 |
<string><html><head/><body><p><span style=" font-size:12pt;">This button clears all highlighted guides from the gene viewer box.</span></p></body></html></string>
|
| 126 |
</property>
|
|
|
|
| 129 |
</property>
|
| 130 |
</widget>
|
| 131 |
</item>
|
| 132 |
+
<item row="6" column="2">
|
| 133 |
+
<widget class="QLabel" name="lblMinOTScore">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
<property name="text">
|
| 135 |
+
<string>Minimum On-Target Score</string>
|
| 136 |
</property>
|
| 137 |
</widget>
|
| 138 |
</item>
|
| 139 |
+
<item row="11" column="0" colspan="4">
|
| 140 |
+
<widget class="QTableWidget" name="tblGuides"/>
|
| 141 |
+
</item>
|
| 142 |
+
<item row="6" column="3">
|
| 143 |
+
<widget class="QSpinBox" name="spnMinOTScore"/>
|
| 144 |
+
</item>
|
| 145 |
+
<item row="5" column="0">
|
| 146 |
+
<widget class="QLabel" name="lblEndonuclease">
|
| 147 |
<property name="text">
|
| 148 |
+
<string>Endonuclease:</string>
|
| 149 |
</property>
|
| 150 |
</widget>
|
| 151 |
</item>
|
| 152 |
+
<item row="12" column="0">
|
| 153 |
+
<widget class="QPushButton" name="pbtnScoringOptions">
|
| 154 |
+
<property name="text">
|
| 155 |
+
<string>Scoring Options</string>
|
|
|
|
|
|
|
|
|
|
| 156 |
</property>
|
| 157 |
</widget>
|
| 158 |
</item>
|
| 159 |
+
<item row="6" column="0">
|
| 160 |
+
<widget class="QCheckBox" name="chkFilter5PrimeG">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
<property name="text">
|
| 162 |
+
<string>Filter 5' G Sequences</string>
|
| 163 |
</property>
|
| 164 |
</widget>
|
| 165 |
</item>
|
| 166 |
+
<item row="10" column="0">
|
| 167 |
+
<widget class="QCheckBox" name="chkSelectAll">
|
| 168 |
+
<property name="text">
|
| 169 |
+
<string>Select All</string>
|
|
|
|
|
|
|
|
|
|
| 170 |
</property>
|
| 171 |
</widget>
|
| 172 |
</item>
|
| 173 |
+
<item row="4" column="0">
|
| 174 |
+
<widget class="QLabel" name="lblGene">
|
| 175 |
<property name="text">
|
| 176 |
+
<string>Gene:</string>
|
| 177 |
</property>
|
| 178 |
</widget>
|
| 179 |
</item>
|
| 180 |
+
<item row="12" column="1">
|
| 181 |
+
<widget class="QPushButton" name="pbtnOffTarget">
|
| 182 |
+
<property name="toolTip">
|
| 183 |
+
<string><html><head/><body><p><span style=" font-size:12pt;">Perform off-target analysis on the selected guides.</span></p></body></html></string>
|
|
|
|
|
|
|
|
|
|
| 184 |
</property>
|
| 185 |
+
<property name="text">
|
| 186 |
+
<string>Off-Target</string>
|
| 187 |
+
</property>
|
| 188 |
+
</widget>
|
| 189 |
+
</item>
|
| 190 |
+
<item row="12" column="2">
|
| 191 |
+
<widget class="QPushButton" name="pbtnCoTargeting">
|
| 192 |
<property name="toolTip">
|
| 193 |
+
<string><html><head/><body><p>Determine guides with synergistic PAMs (Ex. saCas9 and spCas9 compatible guides). <span style=" font-weight:600;">Note:</span> to analyze an organism for co-targeting guides, separate CSPR files must be generated for each additional endonuclease. Co-targeting endonucleases must have the same PAM directionality, same total gRNA length, and overlapping PAM sequences to be compatible.</p></body></html></string>
|
| 194 |
</property>
|
| 195 |
<property name="text">
|
| 196 |
+
<string>Co-Targeting</string>
|
| 197 |
</property>
|
| 198 |
</widget>
|
| 199 |
</item>
|
| 200 |
+
<item row="12" column="3">
|
| 201 |
+
<widget class="QPushButton" name="pbtnExportSelectedgRNAs">
|
| 202 |
+
<property name="toolTip">
|
| 203 |
+
<string><html><head/><body><p><span style=" font-size:12pt;">Export selected guides to a CSV file.</span></p></body></html></string>
|
| 204 |
+
</property>
|
| 205 |
<property name="text">
|
| 206 |
+
<string>Export Selected gRNAs</string>
|
| 207 |
</property>
|
| 208 |
</widget>
|
| 209 |
</item>
|
|
@@ -1,7 +1,7 @@
|
|
| 1 |
-
from PyQt6 import QtWidgets
|
| 2 |
import traceback
|
| 3 |
-
import
|
| 4 |
-
|
| 5 |
|
| 6 |
def show_message(title, message, fontSize=12, icon=QtWidgets.QMessageBox.Icon.Information, button=QtWidgets.QMessageBox.StandardButton.Close):
|
| 7 |
try:
|
|
@@ -15,20 +15,27 @@ def show_message(title, message, fontSize=12, icon=QtWidgets.QMessageBox.Icon.In
|
|
| 15 |
except Exception as e:
|
| 16 |
print(f"Error showing message: {e}")
|
| 17 |
|
| 18 |
-
def show_error(
|
| 19 |
-
|
| 20 |
-
|
|
|
|
|
|
|
| 21 |
logger.critical(message)
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
title="Fatal Error",
|
| 29 |
-
message=f"Fatal Error:\n{str(e)}\n\nFor more information on this error, look at CASPER.log in the application folder."
|
| 30 |
-
)
|
| 31 |
-
except Exception as e:
|
| 32 |
-
print(f"Error showing error message: {e}")
|
| 33 |
|
| 34 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from PyQt6 import QtWidgets
|
| 2 |
import traceback
|
| 3 |
+
import sys
|
| 4 |
+
from PyQt6.QtWidgets import QMessageBox
|
| 5 |
|
| 6 |
def show_message(title, message, fontSize=12, icon=QtWidgets.QMessageBox.Icon.Information, button=QtWidgets.QMessageBox.StandardButton.Close):
|
| 7 |
try:
|
|
|
|
| 15 |
except Exception as e:
|
| 16 |
print(f"Error showing message: {e}")
|
| 17 |
|
| 18 |
+
def show_error(global_settings, message, exception=None):
|
| 19 |
+
"""Show error dialog and log the error"""
|
| 20 |
+
logger = global_settings.get_logger() if global_settings else None
|
| 21 |
+
|
| 22 |
+
if logger:
|
| 23 |
logger.critical(message)
|
| 24 |
+
if exception:
|
| 25 |
+
if isinstance(exception, str):
|
| 26 |
+
logger.critical(exception)
|
| 27 |
+
else:
|
| 28 |
+
logger.critical(str(exception))
|
| 29 |
+
logger.critical(''.join(traceback.format_tb(exception.__traceback__)))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
+
error_box = QMessageBox()
|
| 32 |
+
error_box.setIcon(QMessageBox.Icon.Critical)
|
| 33 |
+
error_box.setText(message)
|
| 34 |
+
if exception:
|
| 35 |
+
if isinstance(exception, str):
|
| 36 |
+
error_box.setDetailedText(exception)
|
| 37 |
+
else:
|
| 38 |
+
error_box.setDetailedText(f"{str(exception)}\n\n{''.join(traceback.format_tb(exception.__traceback__))}")
|
| 39 |
+
error_box.exec()
|
| 40 |
+
|
| 41 |
+
sys.exit(1)
|
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Optional
|
| 2 |
+
from PyQt6 import QtWidgets, uic
|
| 3 |
+
from PyQt6.QtWidgets import QTableWidgetItem, QAbstractItemView, QMessageBox
|
| 4 |
+
from utils.ui import show_error
|
| 5 |
+
|
| 6 |
+
class CoTargetingView(QtWidgets.QMainWindow):
|
| 7 |
+
def __init__(self, global_settings):
|
| 8 |
+
super().__init__()
|
| 9 |
+
self.settings = global_settings
|
| 10 |
+
self.logger = global_settings.get_logger()
|
| 11 |
+
self.init_ui()
|
| 12 |
+
|
| 13 |
+
def init_ui(self):
|
| 14 |
+
try:
|
| 15 |
+
uic.loadUi(self.settings.get_ui_dir_path() + '/cotargeting.ui', self)
|
| 16 |
+
self.setWindowTitle("Co-targeting")
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
self.line_edit_organism = self._find_widget('ledOrganism', QtWidgets.QLineEdit)
|
| 20 |
+
self.table_endonucleases = self._find_widget('tblEndonucleases', QtWidgets.QTableWidget)
|
| 21 |
+
|
| 22 |
+
self.push_button_cancel = self._find_widget('pbtnCancel', QtWidgets.QPushButton)
|
| 23 |
+
self.push_button_submit = self._find_widget('pbtnSubmit', QtWidgets.QPushButton)
|
| 24 |
+
|
| 25 |
+
# Initialize table
|
| 26 |
+
self.table_endonucleases.setColumnCount(1)
|
| 27 |
+
self.table_endonucleases.setShowGrid(True)
|
| 28 |
+
self.table_endonucleases.setHorizontalHeaderLabels(["Endonuclease"])
|
| 29 |
+
self.table_endonucleases.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
| 30 |
+
self.table_endonucleases.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
|
| 31 |
+
self.table_endonucleases.setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection)
|
| 32 |
+
self.table_endonucleases.horizontalHeader().setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeMode.Stretch)
|
| 33 |
+
|
| 34 |
+
except Exception as e:
|
| 35 |
+
show_error(self.settings, "Error initializing CoTargeting UI", str(e))
|
| 36 |
+
|
| 37 |
+
def _find_widget(self, name: str, widget_type: type) -> Optional[QtWidgets.QWidget]:
|
| 38 |
+
widget = self.findChild(widget_type, name)
|
| 39 |
+
if widget is None:
|
| 40 |
+
self.logger.warning(f"Widget '{name}' not found in UI file.")
|
| 41 |
+
return widget
|
| 42 |
+
|
| 43 |
+
def populate_table(self, endo_choices):
|
| 44 |
+
"""Populate table with endonuclease choices"""
|
| 45 |
+
try:
|
| 46 |
+
# Filter original endonucleases (no co-targeted ones)
|
| 47 |
+
filtered_endos = [item for item in endo_choices
|
| 48 |
+
if len(item.split(",")) == 1 and "|" not in item]
|
| 49 |
+
|
| 50 |
+
self.table_endonucleases.setRowCount(len(filtered_endos))
|
| 51 |
+
for i, endo in enumerate(filtered_endos):
|
| 52 |
+
self.table_endonucleases.setItem(i, 0, QTableWidgetItem(endo))
|
| 53 |
+
|
| 54 |
+
self.table_endonucleases.resizeColumnsToContents()
|
| 55 |
+
|
| 56 |
+
except Exception as e:
|
| 57 |
+
show_error(self.settings, "Error populating table", str(e))
|
| 58 |
+
|
| 59 |
+
def get_selected_endonucleases(self):
|
| 60 |
+
"""Get list of selected endonucleases"""
|
| 61 |
+
try:
|
| 62 |
+
selected = []
|
| 63 |
+
for item in self.table_endonucleases.selectedItems():
|
| 64 |
+
selected.append(item.text())
|
| 65 |
+
return selected
|
| 66 |
+
except Exception as e:
|
| 67 |
+
self.logger.error(f"Error getting selected endonucleases: {str(e)}")
|
| 68 |
+
return []
|
| 69 |
+
|
| 70 |
+
def show_error(self, title, message):
|
| 71 |
+
"""Show error message"""
|
| 72 |
+
QMessageBox.critical(self, title, message)
|
| 73 |
+
|
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Optional
|
| 2 |
+
from PyQt6.QtWidgets import QMainWindow
|
| 3 |
+
from PyQt6 import uic, QtWidgets
|
| 4 |
+
import os
|
| 5 |
+
|
| 6 |
+
class ExportSelectedgRNAsView(QMainWindow):
|
| 7 |
+
def __init__(self, global_settings):
|
| 8 |
+
super().__init__()
|
| 9 |
+
self.settings = global_settings
|
| 10 |
+
self.logger = self.settings.get_logger()
|
| 11 |
+
self._init_ui()
|
| 12 |
+
|
| 13 |
+
def _init_ui(self) -> None:
|
| 14 |
+
try:
|
| 15 |
+
uic.loadUi(self.settings.get_ui_dir_path() + '/export_selected_gRNAs.ui', self)
|
| 16 |
+
self.setWindowTitle("Export Selected gRNAs")
|
| 17 |
+
self._init_ui_components()
|
| 18 |
+
except Exception as e:
|
| 19 |
+
self.logger.error(f"Error initializing ExportSelectedgRNAsView: {str(e)}", exc_info=True)
|
| 20 |
+
raise
|
| 21 |
+
|
| 22 |
+
def _init_ui_components(self) -> None:
|
| 23 |
+
self._init_grpExportSettings()
|
| 24 |
+
self._init_grpGuideOptions()
|
| 25 |
+
|
| 26 |
+
self.push_button_cancel = self._find_widget('pbtnCancel', QtWidgets.QPushButton)
|
| 27 |
+
self.push_button_export = self._find_widget('pbtnExport', QtWidgets.QPushButton)
|
| 28 |
+
|
| 29 |
+
def _init_grpExportSettings(self) -> None:
|
| 30 |
+
self.line_edit_file_path = self._find_widget('ledFilePath', QtWidgets.QLineEdit)
|
| 31 |
+
self.push_button_browse = self._find_widget('pbtnBrowse', QtWidgets.QPushButton)
|
| 32 |
+
self.line_edit_file_name = self._find_widget('ledFileName', QtWidgets.QLineEdit)
|
| 33 |
+
self.combo_box_delimiter = self._find_widget('cmbDelimiter', QtWidgets.QComboBox)
|
| 34 |
+
|
| 35 |
+
def _init_grpGuideOptions(self) -> None:
|
| 36 |
+
self.line_edit_leading_sequence = self._find_widget('ledLeadingsequence', QtWidgets.QLineEdit)
|
| 37 |
+
self.line_edit_trailing_sequence = self._find_widget('ledTrailingSequence', QtWidgets.QLineEdit)
|
| 38 |
+
|
| 39 |
+
def _find_widget(self, name: str, widget_type: type) -> Optional[QtWidgets.QWidget]:
|
| 40 |
+
widget = self.findChild(widget_type, name)
|
| 41 |
+
if widget is None:
|
| 42 |
+
self.logger.warning(f"Widget '{name}' not found in UI file.")
|
| 43 |
+
return widget
|
| 44 |
+
|
| 45 |
+
def show_dialog(self) -> None:
|
| 46 |
+
self.show()
|
| 47 |
+
self.activateWindow()
|
| 48 |
+
|
| 49 |
+
def get_export_settings(self) -> dict:
|
| 50 |
+
return {
|
| 51 |
+
'file_path': self.line_edit_file_path.text(),
|
| 52 |
+
'file_name': self.line_edit_file_name.text(),
|
| 53 |
+
'delimiter': self.combo_box_delimiter.currentText(),
|
| 54 |
+
'leading_sequence': self.line_edit_leading_sequence.text().strip(),
|
| 55 |
+
'trailing_sequence': self.line_edit_trailing_sequence.text().strip()
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
def set_file_path(self, path: str) -> None:
|
| 59 |
+
self.line_edit_file_path.setText(path)
|
|
@@ -27,30 +27,29 @@ class FindTargetsView(QtWidgets.QMainWindow):
|
|
| 27 |
self.results_table.setVerticalScrollMode(QTableWidget.ScrollMode.ScrollPerPixel)
|
| 28 |
self.results_table.setHorizontalScrollMode(QTableWidget.ScrollMode.ScrollPerPixel)
|
| 29 |
|
| 30 |
-
# Optimize viewport updates
|
| 31 |
self.results_table.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
|
| 32 |
self.results_table.viewport().setProperty("cursor", Qt.CursorShape.ArrowCursor)
|
| 33 |
|
| 34 |
-
|
| 35 |
-
self.results_table.setColumnCount(5) # Reduced from 7 to 5 columns
|
| 36 |
headers = [
|
| 37 |
"Feature Type", "Chromosome/Scaffold #", "Feature ID/Locus Tag",
|
| 38 |
"Feature Name", "Feature Description"
|
| 39 |
]
|
| 40 |
self.results_table.setHorizontalHeaderLabels(headers)
|
| 41 |
|
| 42 |
-
|
| 43 |
-
column_widths = [100, 150, 150, 150, 300] # Adjusted widths
|
| 44 |
for i, width in enumerate(column_widths):
|
| 45 |
self.results_table.setColumnWidth(i, width)
|
| 46 |
|
| 47 |
self.results_table.horizontalHeader().setStretchLastSection(True)
|
| 48 |
|
| 49 |
-
# Connect scroll events for virtual scrolling
|
| 50 |
self.results_table.verticalScrollBar().valueChanged.connect(self._handle_scroll)
|
| 51 |
-
|
|
|
|
| 52 |
self.push_button_view_targets = self.findChild(QPushButton, 'pbtnViewTargets')
|
| 53 |
|
|
|
|
|
|
|
| 54 |
def _create_table_item(self, text):
|
| 55 |
"""Optimized item creation"""
|
| 56 |
item = QTableWidgetItem(str(text))
|
|
@@ -146,3 +145,35 @@ class FindTargetsView(QtWidgets.QMainWindow):
|
|
| 146 |
self._all_results = []
|
| 147 |
self._loaded_rows = 0
|
| 148 |
self.results_table.setUpdatesEnabled(True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
self.results_table.setVerticalScrollMode(QTableWidget.ScrollMode.ScrollPerPixel)
|
| 28 |
self.results_table.setHorizontalScrollMode(QTableWidget.ScrollMode.ScrollPerPixel)
|
| 29 |
|
|
|
|
| 30 |
self.results_table.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
|
| 31 |
self.results_table.viewport().setProperty("cursor", Qt.CursorShape.ArrowCursor)
|
| 32 |
|
| 33 |
+
self.results_table.setColumnCount(5)
|
|
|
|
| 34 |
headers = [
|
| 35 |
"Feature Type", "Chromosome/Scaffold #", "Feature ID/Locus Tag",
|
| 36 |
"Feature Name", "Feature Description"
|
| 37 |
]
|
| 38 |
self.results_table.setHorizontalHeaderLabels(headers)
|
| 39 |
|
| 40 |
+
column_widths = [100, 150, 150, 150, 300]
|
|
|
|
| 41 |
for i, width in enumerate(column_widths):
|
| 42 |
self.results_table.setColumnWidth(i, width)
|
| 43 |
|
| 44 |
self.results_table.horizontalHeader().setStretchLastSection(True)
|
| 45 |
|
|
|
|
| 46 |
self.results_table.verticalScrollBar().valueChanged.connect(self._handle_scroll)
|
| 47 |
+
|
| 48 |
+
self.push_button_generate_library = self.findChild(QPushButton, 'pbtnGenerateLibrary')
|
| 49 |
self.push_button_view_targets = self.findChild(QPushButton, 'pbtnViewTargets')
|
| 50 |
|
| 51 |
+
self.push_button_generate_library.clicked.connect(self._on_generate_library_clicked)
|
| 52 |
+
|
| 53 |
def _create_table_item(self, text):
|
| 54 |
"""Optimized item creation"""
|
| 55 |
item = QTableWidgetItem(str(text))
|
|
|
|
| 145 |
self._all_results = []
|
| 146 |
self._loaded_rows = 0
|
| 147 |
self.results_table.setUpdatesEnabled(True)
|
| 148 |
+
|
| 149 |
+
def _on_generate_library_clicked(self):
|
| 150 |
+
"""Handle generate library button click"""
|
| 151 |
+
try:
|
| 152 |
+
selected_targets = self.get_selected_targets()
|
| 153 |
+
self.global_settings.logger.debug(f"Selected {len(selected_targets)} targets for library generation")
|
| 154 |
+
|
| 155 |
+
if not selected_targets:
|
| 156 |
+
QtWidgets.QMessageBox.warning(
|
| 157 |
+
self,
|
| 158 |
+
"No Selection",
|
| 159 |
+
"Please select targets to generate library."
|
| 160 |
+
)
|
| 161 |
+
return
|
| 162 |
+
|
| 163 |
+
# Create and show generate library window
|
| 164 |
+
self.global_settings.logger.debug("Creating GenerateLibraryController")
|
| 165 |
+
from controllers.GenerateLibraryController import GenerateLibraryController
|
| 166 |
+
generate_library_controller = GenerateLibraryController(
|
| 167 |
+
self.global_settings,
|
| 168 |
+
selected_targets
|
| 169 |
+
)
|
| 170 |
+
self.global_settings.logger.debug("Showing generate library window")
|
| 171 |
+
generate_library_controller.show()
|
| 172 |
+
|
| 173 |
+
except Exception as e:
|
| 174 |
+
self.global_settings.logger.error(f"Error in generate library: {str(e)}")
|
| 175 |
+
QtWidgets.QMessageBox.critical(
|
| 176 |
+
self,
|
| 177 |
+
"Error",
|
| 178 |
+
f"An error occurred while opening the generate library window: {str(e)}"
|
| 179 |
+
)
|
|
@@ -1,82 +0,0 @@
|
|
| 1 |
-
__author__ = 'brianmendoza'
|
| 2 |
-
|
| 3 |
-
from Bio import Entrez, SeqIO
|
| 4 |
-
import webbrowser
|
| 5 |
-
import re
|
| 6 |
-
import os
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
class GenBankFile:
|
| 10 |
-
|
| 11 |
-
def __init__(self, organism):
|
| 12 |
-
Entrez.email = "bmendoz1@vols.utk.edu"
|
| 13 |
-
self.directory = "/Users/brianmendoza/Desktop/GenBank_files/"
|
| 14 |
-
self.org = organism
|
| 15 |
-
|
| 16 |
-
def setOrg(self, org):
|
| 17 |
-
self.org = org
|
| 18 |
-
|
| 19 |
-
def setDirectory(self, path, org):
|
| 20 |
-
self.directory = path
|
| 21 |
-
self.setOrg(org)
|
| 22 |
-
|
| 23 |
-
def convertToFasta(self):
|
| 24 |
-
orgfile = self.directory + self.org + ".gbff"
|
| 25 |
-
output = "/Users/brianmendoza/Desktop/GenBank_files/FASTAs/" + self.org + ".fna"
|
| 26 |
-
SeqIO.convert(orgfile, "genbank", output, "fasta")
|
| 27 |
-
|
| 28 |
-
def parseAnnotation(self):
|
| 29 |
-
gb_file = self.directory + self.org + ".gbff"
|
| 30 |
-
records = SeqIO.parse(open(gb_file,"r"), "genbank")
|
| 31 |
-
|
| 32 |
-
# create table for multi-targeting reference
|
| 33 |
-
table = {}
|
| 34 |
-
count = 0
|
| 35 |
-
for record in records:
|
| 36 |
-
count += 1
|
| 37 |
-
chrmnumber = str(count)
|
| 38 |
-
table[chrmnumber] = []
|
| 39 |
-
for feature in record.features:
|
| 40 |
-
if feature.type == 'CDS': # hopefully gene and CDS are the same
|
| 41 |
-
|
| 42 |
-
# getting the location...
|
| 43 |
-
loc = str(feature.location)
|
| 44 |
-
out = re.findall(r"[\d]+", loc)
|
| 45 |
-
start = out[0]
|
| 46 |
-
end = out[1]
|
| 47 |
-
if len(out) > 2: # to account for "joined" domains
|
| 48 |
-
end = out[3]
|
| 49 |
-
|
| 50 |
-
# locus_tag and product...
|
| 51 |
-
if 'locus_tag' in feature.qualifiers:
|
| 52 |
-
ltag = feature.qualifiers['locus_tag']
|
| 53 |
-
elif 'gene' in feature.qualifiers:
|
| 54 |
-
ltag = feature.qualifiers['gene']
|
| 55 |
-
if 'product' not in feature.qualifiers:
|
| 56 |
-
prod = feature.qualifiers['note']
|
| 57 |
-
else:
|
| 58 |
-
prod = feature.qualifiers['product']
|
| 59 |
-
# adding it all up...
|
| 60 |
-
tup = (start, end, ltag, prod)
|
| 61 |
-
table[chrmnumber].append(tup)
|
| 62 |
-
return table
|
| 63 |
-
|
| 64 |
-
def getChromSequence(self, index):
|
| 65 |
-
gb_file = self.directory + self.org + ".gbff"
|
| 66 |
-
records = SeqIO.parse(open(gb_file,"r"), "genbank")
|
| 67 |
-
count = 0
|
| 68 |
-
for record in records:
|
| 69 |
-
count += 1
|
| 70 |
-
if count == index:
|
| 71 |
-
cstr = record.seq
|
| 72 |
-
return cstr
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
class GffFile:
|
| 76 |
-
|
| 77 |
-
def __init__(self, organism):
|
| 78 |
-
self.directory = "/Users/brianmendoza/Desktop/GenBank_Files/"
|
| 79 |
-
self.org = organism
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -0,0 +1,182 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from PyQt6 import QtWidgets, uic
|
| 2 |
+
from PyQt6.QtWidgets import QMainWindow, QFileDialog, QWidget
|
| 3 |
+
from PyQt6.QtCore import pyqtSignal, Qt
|
| 4 |
+
import os
|
| 5 |
+
import platform
|
| 6 |
+
|
| 7 |
+
class GenerateLibraryView(QMainWindow):
|
| 8 |
+
# Define signals
|
| 9 |
+
submit_clicked = pyqtSignal(dict) # Signal to emit settings dict when submit is clicked
|
| 10 |
+
progress_updated = pyqtSignal(int)
|
| 11 |
+
|
| 12 |
+
def __init__(self, global_settings):
|
| 13 |
+
super().__init__()
|
| 14 |
+
self.global_settings = global_settings
|
| 15 |
+
self.logger = global_settings.logger
|
| 16 |
+
self._init_ui()
|
| 17 |
+
self._connect_signals()
|
| 18 |
+
|
| 19 |
+
# Set window properties
|
| 20 |
+
self.setWindowModality(Qt.WindowModality.ApplicationModal) # Make window modal
|
| 21 |
+
self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, True) # Clean up on close
|
| 22 |
+
|
| 23 |
+
def _init_ui(self):
|
| 24 |
+
try:
|
| 25 |
+
# Load UI file
|
| 26 |
+
ui_file = os.path.join(self.global_settings.get_ui_dir_path(), 'generate_library.ui')
|
| 27 |
+
self.logger.debug(f"Loading UI file from: {ui_file}")
|
| 28 |
+
uic.loadUi(ui_file, self)
|
| 29 |
+
|
| 30 |
+
# Set window properties
|
| 31 |
+
self.setWindowTitle("Generate Library")
|
| 32 |
+
self.setMinimumSize(800, 600) # Set minimum size
|
| 33 |
+
|
| 34 |
+
# Initialize comboboxes
|
| 35 |
+
self._init_guides_per_gene_combo()
|
| 36 |
+
self._init_min_score_combo()
|
| 37 |
+
|
| 38 |
+
# Set default values
|
| 39 |
+
self.ledTargetRangeStart.setText('0')
|
| 40 |
+
self.ledTargetRangeEnd.setText('100')
|
| 41 |
+
self.ledSpaceBetweenGuides.setText('15')
|
| 42 |
+
|
| 43 |
+
# Set default file path
|
| 44 |
+
default_path = self.global_settings.get_db_path()
|
| 45 |
+
if platform.system() == "Windows":
|
| 46 |
+
self.ledFilePath.setText(default_path + "\\")
|
| 47 |
+
else:
|
| 48 |
+
self.ledFilePath.setText(default_path + "/")
|
| 49 |
+
|
| 50 |
+
# Center the window
|
| 51 |
+
self._center_window()
|
| 52 |
+
|
| 53 |
+
except Exception as e:
|
| 54 |
+
self.logger.error(f"Error initializing GenerateLibraryView UI: {str(e)}")
|
| 55 |
+
raise
|
| 56 |
+
|
| 57 |
+
def _center_window(self):
|
| 58 |
+
"""Center the window on the screen"""
|
| 59 |
+
try:
|
| 60 |
+
screen = QtWidgets.QApplication.primaryScreen().geometry()
|
| 61 |
+
size = self.geometry()
|
| 62 |
+
x = (screen.width() - size.width()) // 2
|
| 63 |
+
y = (screen.height() - size.height()) // 2
|
| 64 |
+
self.move(x, y)
|
| 65 |
+
except Exception as e:
|
| 66 |
+
self.logger.error(f"Error centering window: {str(e)}")
|
| 67 |
+
|
| 68 |
+
def _init_guides_per_gene_combo(self):
|
| 69 |
+
"""Initialize guides per gene combobox"""
|
| 70 |
+
try:
|
| 71 |
+
self.cmbGuidesPerGene.clear()
|
| 72 |
+
for i in range(1, 11):
|
| 73 |
+
self.cmbGuidesPerGene.addItem(str(i))
|
| 74 |
+
except Exception as e:
|
| 75 |
+
self.logger.error(f"Error initializing guides per gene combo: {str(e)}")
|
| 76 |
+
|
| 77 |
+
def _init_min_score_combo(self):
|
| 78 |
+
"""Initialize minimum on-target score combobox"""
|
| 79 |
+
try:
|
| 80 |
+
self.cmbMinimumOnTargetScore.clear()
|
| 81 |
+
for i in range(20, 71):
|
| 82 |
+
self.cmbMinimumOnTargetScore.addItem(str(i))
|
| 83 |
+
except Exception as e:
|
| 84 |
+
self.logger.error(f"Error initializing min score combo: {str(e)}")
|
| 85 |
+
|
| 86 |
+
def _connect_signals(self):
|
| 87 |
+
"""Connect button signals"""
|
| 88 |
+
try:
|
| 89 |
+
self.pbtnBrowse.clicked.connect(self._browse_output_path)
|
| 90 |
+
self.pbtnCancel.clicked.connect(self.close)
|
| 91 |
+
self.pbtnSubmit.clicked.connect(self._on_submit)
|
| 92 |
+
except Exception as e:
|
| 93 |
+
self.logger.error(f"Error connecting signals: {str(e)}")
|
| 94 |
+
|
| 95 |
+
def showEvent(self, event):
|
| 96 |
+
"""Override showEvent to ensure proper window display"""
|
| 97 |
+
super().showEvent(event)
|
| 98 |
+
self.raise_() # Bring window to front
|
| 99 |
+
self.activateWindow() # Activate the window
|
| 100 |
+
|
| 101 |
+
def closeEvent(self, event):
|
| 102 |
+
"""Override closeEvent to ensure proper cleanup"""
|
| 103 |
+
try:
|
| 104 |
+
self.logger.debug("Closing GenerateLibraryView")
|
| 105 |
+
super().closeEvent(event)
|
| 106 |
+
except Exception as e:
|
| 107 |
+
self.logger.error(f"Error in closeEvent: {str(e)}")
|
| 108 |
+
|
| 109 |
+
def _browse_output_path(self):
|
| 110 |
+
"""Handle browse button click"""
|
| 111 |
+
folder = QFileDialog.getExistingDirectory(
|
| 112 |
+
self,
|
| 113 |
+
"Select Output Directory",
|
| 114 |
+
self.global_settings.get_db_path(),
|
| 115 |
+
QFileDialog.Option.ShowDirsOnly
|
| 116 |
+
)
|
| 117 |
+
|
| 118 |
+
if folder:
|
| 119 |
+
if platform.system() == "Windows":
|
| 120 |
+
self.ledFilePath.setText(folder + "\\")
|
| 121 |
+
else:
|
| 122 |
+
self.ledFilePath.setText(folder + "/")
|
| 123 |
+
|
| 124 |
+
def get_library_settings(self):
|
| 125 |
+
"""Get all settings from the UI"""
|
| 126 |
+
try:
|
| 127 |
+
settings = {
|
| 128 |
+
'guides_per_gene': int(self.cmbGuidesPerGene.currentText()),
|
| 129 |
+
'target_range_start': float(self.ledTargetRangeStart.text()),
|
| 130 |
+
'target_range_end': float(self.ledTargetRangeEnd.text()),
|
| 131 |
+
'space_between_guides': int(self.ledSpaceBetweenGuides.text() or '15'),
|
| 132 |
+
'min_score': int(self.cmbMinimumOnTargetScore.currentText()),
|
| 133 |
+
'find_off_targets': self.chkFindOffTargets.isChecked(),
|
| 134 |
+
'modify_params': self.chkModifyParameters.isChecked(),
|
| 135 |
+
'five_prime_seq': self.led5PrimeSpecificity.text(),
|
| 136 |
+
'output_file': os.path.join(
|
| 137 |
+
self.ledFilePath.text(),
|
| 138 |
+
self.ledFileName.text()
|
| 139 |
+
)
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
if self.chkFindOffTargets.isChecked():
|
| 143 |
+
try:
|
| 144 |
+
settings['max_off_target_score'] = float(self.cmbMaximumOffTargetScore.text())
|
| 145 |
+
except ValueError:
|
| 146 |
+
raise ValueError("Invalid maximum off-target score")
|
| 147 |
+
|
| 148 |
+
return settings
|
| 149 |
+
|
| 150 |
+
except ValueError as e:
|
| 151 |
+
raise ValueError(f"Invalid input: {str(e)}")
|
| 152 |
+
except Exception as e:
|
| 153 |
+
raise ValueError(f"Error getting settings: {str(e)}")
|
| 154 |
+
|
| 155 |
+
def update_progress(self, value):
|
| 156 |
+
"""Update progress bar"""
|
| 157 |
+
self.progBar.setValue(value)
|
| 158 |
+
|
| 159 |
+
def _on_submit(self):
|
| 160 |
+
"""Validate and emit submit signal"""
|
| 161 |
+
try:
|
| 162 |
+
settings = self.get_library_settings()
|
| 163 |
+
# Emit the settings through the signal
|
| 164 |
+
self.submit_clicked.emit(settings)
|
| 165 |
+
except ValueError as e:
|
| 166 |
+
QtWidgets.QMessageBox.critical(
|
| 167 |
+
self,
|
| 168 |
+
"Invalid Input",
|
| 169 |
+
str(e)
|
| 170 |
+
)
|
| 171 |
+
|
| 172 |
+
def show_error(self, title, message):
|
| 173 |
+
"""Show error message"""
|
| 174 |
+
QtWidgets.QMessageBox.critical(self, title, message)
|
| 175 |
+
|
| 176 |
+
def show_success(self, message):
|
| 177 |
+
"""Show success message"""
|
| 178 |
+
QtWidgets.QMessageBox.information(
|
| 179 |
+
self,
|
| 180 |
+
"Success",
|
| 181 |
+
message
|
| 182 |
+
)
|
|
@@ -12,7 +12,7 @@ class HomeWindowView(QWidget):
|
|
| 12 |
|
| 13 |
def _init_ui(self) -> None:
|
| 14 |
try:
|
| 15 |
-
uic.loadUi(os.path.join(self.global_settings.get_ui_dir_path(), "
|
| 16 |
self._init_ui_elements()
|
| 17 |
except Exception as e:
|
| 18 |
self._handle_init_error(e)
|
|
@@ -38,6 +38,10 @@ class HomeWindowView(QWidget):
|
|
| 38 |
self._init_grpStep2()
|
| 39 |
self._init_grpStep3()
|
| 40 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
def _init_grpNavigationMenu(self) -> None:
|
| 42 |
self.push_button_new_genome = self._find_widget("pbtnNewGenome", QPushButton)
|
| 43 |
self.push_button_new_endonuclease = self._find_widget("pbtnNewEndonuclease", QPushButton)
|
|
@@ -58,10 +62,7 @@ class HomeWindowView(QWidget):
|
|
| 58 |
self.radio_button_position = self._find_widget("rbtnPosition", QRadioButton)
|
| 59 |
self.radio_button_sequence = self._find_widget("rbtnSequence", QRadioButton)
|
| 60 |
self.text_edit_gene_entry = self._find_widget("txtedGeneEntry", QPlainTextEdit)
|
| 61 |
-
self.
|
| 62 |
-
self.progress_bar_find_targets = self._find_widget("progBarFindTargets", QProgressBar)
|
| 63 |
-
self.push_button_view_targets = self._find_widget("pbtnViewTargets", QPushButton)
|
| 64 |
-
self.push_button_generate_library = self._find_widget("pbtnGenerateLibrary", QPushButton)
|
| 65 |
|
| 66 |
placeholder_text = ("Example Inputs: \n\n"
|
| 67 |
"Option 1: Feature (ID, Locus Tag, or Name)\n"
|
|
@@ -101,12 +102,6 @@ class HomeWindowView(QWidget):
|
|
| 101 |
# self.combo_box_local_annotation_files.clear()
|
| 102 |
# self.combo_box_local_annotation_files.addItems(annotation_files)
|
| 103 |
|
| 104 |
-
def set_progress_bar(self, value: int) -> None:
|
| 105 |
-
self.progress_bar_find_targets.setValue(value)
|
| 106 |
-
|
| 107 |
-
def reset_progress_bar(self) -> None:
|
| 108 |
-
self.set_progress_bar(0)
|
| 109 |
-
|
| 110 |
def get_find_targets_input(self) -> dict:
|
| 111 |
return {
|
| 112 |
"organism": self.combo_box_organism.currentText(),
|
|
@@ -147,4 +142,83 @@ class HomeWindowView(QWidget):
|
|
| 147 |
self.logger.debug("No local annotation files found")
|
| 148 |
|
| 149 |
except Exception as e:
|
| 150 |
-
self.logger.error(f"Error updating local annotation files: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
def _init_ui(self) -> None:
|
| 14 |
try:
|
| 15 |
+
uic.loadUi(os.path.join(self.global_settings.get_ui_dir_path(), "home_window.ui"), self)
|
| 16 |
self._init_ui_elements()
|
| 17 |
except Exception as e:
|
| 18 |
self._handle_init_error(e)
|
|
|
|
| 38 |
self._init_grpStep2()
|
| 39 |
self._init_grpStep3()
|
| 40 |
|
| 41 |
+
# Connect to database manager signals
|
| 42 |
+
self.global_settings.db_manager.db_files_changed.connect(self._handle_db_files_changed)
|
| 43 |
+
self.global_settings.db_manager.db_state_changed.connect(self._handle_db_state_changed)
|
| 44 |
+
|
| 45 |
def _init_grpNavigationMenu(self) -> None:
|
| 46 |
self.push_button_new_genome = self._find_widget("pbtnNewGenome", QPushButton)
|
| 47 |
self.push_button_new_endonuclease = self._find_widget("pbtnNewEndonuclease", QPushButton)
|
|
|
|
| 62 |
self.radio_button_position = self._find_widget("rbtnPosition", QRadioButton)
|
| 63 |
self.radio_button_sequence = self._find_widget("rbtnSequence", QRadioButton)
|
| 64 |
self.text_edit_gene_entry = self._find_widget("txtedGeneEntry", QPlainTextEdit)
|
| 65 |
+
self.push_button_find_view_targets = self._find_widget("pbtnFindViewTargets", QPushButton)
|
|
|
|
|
|
|
|
|
|
| 66 |
|
| 67 |
placeholder_text = ("Example Inputs: \n\n"
|
| 68 |
"Option 1: Feature (ID, Locus Tag, or Name)\n"
|
|
|
|
| 102 |
# self.combo_box_local_annotation_files.clear()
|
| 103 |
# self.combo_box_local_annotation_files.addItems(annotation_files)
|
| 104 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
def get_find_targets_input(self) -> dict:
|
| 106 |
return {
|
| 107 |
"organism": self.combo_box_organism.currentText(),
|
|
|
|
| 142 |
self.logger.debug("No local annotation files found")
|
| 143 |
|
| 144 |
except Exception as e:
|
| 145 |
+
self.logger.error(f"Error updating local annotation files: {str(e)}")
|
| 146 |
+
|
| 147 |
+
def show_warning(self, title: str, message: str) -> None:
|
| 148 |
+
"""Show a warning message dialog"""
|
| 149 |
+
QtWidgets.QMessageBox.warning(self, title, message)
|
| 150 |
+
|
| 151 |
+
def _update_cspr_related_ui(self) -> None:
|
| 152 |
+
"""Update UI elements that depend on CSPR files"""
|
| 153 |
+
try:
|
| 154 |
+
# Store current selections
|
| 155 |
+
current_organism = self.combo_box_organism.currentText()
|
| 156 |
+
current_endo = self.combo_box_endonuclease.currentText()
|
| 157 |
+
|
| 158 |
+
# Get fresh data from controller
|
| 159 |
+
controller = self.global_settings.main_window.controller
|
| 160 |
+
organism_to_endonuclease = controller.get_organism_to_endonuclease()
|
| 161 |
+
|
| 162 |
+
# Update organism combo box
|
| 163 |
+
self.combo_box_organism.clear()
|
| 164 |
+
self.combo_box_organism.addItems(sorted(organism_to_endonuclease.keys()))
|
| 165 |
+
|
| 166 |
+
# Restore organism selection if still valid
|
| 167 |
+
if current_organism in organism_to_endonuclease:
|
| 168 |
+
self.combo_box_organism.setCurrentText(current_organism)
|
| 169 |
+
# Restore endonuclease selection if still valid for this organism
|
| 170 |
+
if current_endo in organism_to_endonuclease[current_organism]:
|
| 171 |
+
self.combo_box_endonuclease.setCurrentText(current_endo)
|
| 172 |
+
|
| 173 |
+
except Exception as e:
|
| 174 |
+
self.logger.error(f"Error updating CSPR-related UI: {str(e)}")
|
| 175 |
+
|
| 176 |
+
def _update_gbff_related_ui(self) -> None:
|
| 177 |
+
"""Update UI elements that depend on GBFF files"""
|
| 178 |
+
try:
|
| 179 |
+
# Store current selection
|
| 180 |
+
current_file = self.combo_box_local_annotation_files.currentText()
|
| 181 |
+
|
| 182 |
+
# Update annotation files
|
| 183 |
+
annotation_files = self.global_settings.main_window.controller.get_annotation_files()
|
| 184 |
+
self.update_combo_box_annotation_files(annotation_files)
|
| 185 |
+
|
| 186 |
+
# Restore selection if still valid
|
| 187 |
+
if current_file in annotation_files:
|
| 188 |
+
self.combo_box_local_annotation_files.setCurrentText(current_file)
|
| 189 |
+
|
| 190 |
+
except Exception as e:
|
| 191 |
+
self.logger.error(f"Error updating GBFF-related UI: {str(e)}")
|
| 192 |
+
|
| 193 |
+
def _handle_db_files_changed(self, changes):
|
| 194 |
+
"""Handle database file changes"""
|
| 195 |
+
try:
|
| 196 |
+
if (FileChangeType.CSPR_ADDED in changes or
|
| 197 |
+
FileChangeType.CSPR_REMOVED in changes):
|
| 198 |
+
self._update_cspr_related_ui()
|
| 199 |
+
|
| 200 |
+
if (FileChangeType.GBFF_ADDED in changes or
|
| 201 |
+
FileChangeType.GBFF_REMOVED in changes):
|
| 202 |
+
self._update_gbff_related_ui()
|
| 203 |
+
|
| 204 |
+
except Exception as e:
|
| 205 |
+
self.logger.error(f"Error handling database file changes: {str(e)}")
|
| 206 |
+
|
| 207 |
+
def _handle_db_state_changed(self, is_valid, message, changes):
|
| 208 |
+
"""Handle database state changes"""
|
| 209 |
+
try:
|
| 210 |
+
if not is_valid:
|
| 211 |
+
self.show_warning("Database Warning", message)
|
| 212 |
+
return
|
| 213 |
+
|
| 214 |
+
if changes: # If there are any changes
|
| 215 |
+
if any(change in changes for change in
|
| 216 |
+
[FileChangeType.CSPR_ADDED, FileChangeType.CSPR_REMOVED]):
|
| 217 |
+
self._update_cspr_related_ui()
|
| 218 |
+
|
| 219 |
+
if any(change in changes for change in
|
| 220 |
+
[FileChangeType.GBFF_ADDED, FileChangeType.GBFF_REMOVED]):
|
| 221 |
+
self._update_gbff_related_ui()
|
| 222 |
+
|
| 223 |
+
except Exception as e:
|
| 224 |
+
self.logger.error(f"Error handling database state change: {str(e)}")
|
|
@@ -1,152 +0,0 @@
|
|
| 1 |
-
from PyQt6 import QtWidgets, QtGui, QtCore, uic
|
| 2 |
-
import os
|
| 3 |
-
|
| 4 |
-
class MainWindowUI(QtWidgets.QMainWindow):
|
| 5 |
-
def __init__(self, settings):
|
| 6 |
-
super(MainWindowUI, self).__init__()
|
| 7 |
-
self.settings = settings
|
| 8 |
-
self.setup_ui()
|
| 9 |
-
|
| 10 |
-
def setup_ui(self):
|
| 11 |
-
# Load the UI file
|
| 12 |
-
uic.loadUi(os.path.join(self.settings.get_ui_dir(), 'main_window.ui'), self)
|
| 13 |
-
|
| 14 |
-
# Set window properties
|
| 15 |
-
self.setWindowTitle("CASPER")
|
| 16 |
-
self.setWindowIcon(QtGui.QIcon(os.path.join(self.settings.get_assets_dir(), "cas9image.ico")))
|
| 17 |
-
|
| 18 |
-
# Initialize UI components
|
| 19 |
-
self.init_ui_components()
|
| 20 |
-
|
| 21 |
-
# Set up styles
|
| 22 |
-
self.set_styles()
|
| 23 |
-
|
| 24 |
-
# Initialize progress bar to 0
|
| 25 |
-
self.progressBar.setValue(0)
|
| 26 |
-
|
| 27 |
-
# Add the theme toggle button
|
| 28 |
-
self.theme_toggle_button = QtWidgets.QPushButton(self)
|
| 29 |
-
self.theme_toggle_button.setFixedSize(32, 32)
|
| 30 |
-
self.theme_toggle_button.setStyleSheet("border: none;")
|
| 31 |
-
self.update_theme_icon()
|
| 32 |
-
|
| 33 |
-
# Position the button in the top right corner
|
| 34 |
-
self.theme_toggle_button.setGeometry(self.width() - 40, 10, 32, 32)
|
| 35 |
-
|
| 36 |
-
# Connect the button to a slot (to be implemented in the controller)
|
| 37 |
-
self.theme_toggle_button.clicked.connect(self.on_theme_toggle)
|
| 38 |
-
|
| 39 |
-
def init_ui_components(self):
|
| 40 |
-
# Initialize and find all the UI components
|
| 41 |
-
self.org_choice = self.findChild(QtWidgets.QComboBox, 'orgChoice')
|
| 42 |
-
self.endo_choice = self.findChild(QtWidgets.QComboBox, 'endoChoice')
|
| 43 |
-
self.annotation_files = self.findChild(QtWidgets.QComboBox, 'annotation_files')
|
| 44 |
-
self.gene_entry_field = self.findChild(QtWidgets.QPlainTextEdit, 'gene_entry_field')
|
| 45 |
-
|
| 46 |
-
self.push_button_find_targets = self.findChild(QtWidgets.QPushButton, 'pushButton_FindTargets')
|
| 47 |
-
self.push_button_view_targets = self.findChild(QtWidgets.QPushButton, 'pushButton_ViewTargets')
|
| 48 |
-
self.generate_library = self.findChild(QtWidgets.QPushButton, 'GenerateLibrary')
|
| 49 |
-
|
| 50 |
-
self.radio_button_gene = self.findChild(QtWidgets.QRadioButton, 'radioButton_Gene')
|
| 51 |
-
self.radio_button_position = self.findChild(QtWidgets.QRadioButton, 'radioButton_Position')
|
| 52 |
-
self.radio_button_sequence = self.findChild(QtWidgets.QRadioButton, 'radioButton_Sequence')
|
| 53 |
-
|
| 54 |
-
self.new_genome_button = self.findChild(QtWidgets.QPushButton, 'newGenome_button')
|
| 55 |
-
self.new_endo_button = self.findChild(QtWidgets.QPushButton, 'newEndo_button')
|
| 56 |
-
self.multitargeting_button = self.findChild(QtWidgets.QPushButton, 'multitargeting_button')
|
| 57 |
-
self.population_analysis_button = self.findChild(QtWidgets.QPushButton, 'populationAnalysis_button')
|
| 58 |
-
self.combine_files_button = self.findChild(QtWidgets.QPushButton, 'combineFiles_button')
|
| 59 |
-
|
| 60 |
-
self.progress_bar = self.findChild(QtWidgets.QProgressBar, 'progressBar')
|
| 61 |
-
|
| 62 |
-
self.step1 = self.findChild(QtWidgets.QGroupBox, 'Step1')
|
| 63 |
-
self.step2 = self.findChild(QtWidgets.QGroupBox, 'Step2')
|
| 64 |
-
self.step3 = self.findChild(QtWidgets.QGroupBox, 'Step3')
|
| 65 |
-
self.casper_navigation = self.findChild(QtWidgets.QGroupBox, 'CASPER_Navigation')
|
| 66 |
-
|
| 67 |
-
self.ncbi_button = self.findChild(QtWidgets.QPushButton, 'ncbi_button')
|
| 68 |
-
|
| 69 |
-
# Connect the actionChange_Directory to a slot
|
| 70 |
-
self.actionChange_Directory.triggered.connect(self.on_change_directory)
|
| 71 |
-
|
| 72 |
-
def set_styles(self):
|
| 73 |
-
groupbox_style = """
|
| 74 |
-
QGroupBox:title{subcontrol-origin: margin;
|
| 75 |
-
left: 10px;
|
| 76 |
-
padding: 0 5px 0 5px;}
|
| 77 |
-
QGroupBox#Step1{border: 2px solid rgb(111,181,110);
|
| 78 |
-
border-radius: 9px;
|
| 79 |
-
margin-top: 10px;
|
| 80 |
-
font: bold 14pt 'Arial';}
|
| 81 |
-
"""
|
| 82 |
-
self.step1.setStyleSheet(groupbox_style)
|
| 83 |
-
self.step2.setStyleSheet(groupbox_style.replace("Step1", "Step2"))
|
| 84 |
-
self.step3.setStyleSheet(groupbox_style.replace("Step1", "Step3"))
|
| 85 |
-
self.casper_navigation.setStyleSheet(groupbox_style.replace("Step1", "CASPER_Navigation")
|
| 86 |
-
.replace("solid","dashed")
|
| 87 |
-
.replace("rgb(111,181,110)","rgb(88,89,91)"))
|
| 88 |
-
|
| 89 |
-
def set_gene_entry_placeholder(self):
|
| 90 |
-
placeholder_text = ("Example Inputs: \n\n"
|
| 91 |
-
"Option 1: Feature (ID, Locus Tag, or Name)\n"
|
| 92 |
-
"Example: 854068/YOL086C/ADH1 for S. cerevisiae alcohol dehydrogenase 1\n\n"
|
| 93 |
-
"Option 2: Position (chromosome,start,stop)\n"
|
| 94 |
-
"Example: 1,1,1000 for targeting chromosome 1, base pairs 1 to 1000\n\n"
|
| 95 |
-
"Option 3: Sequence (must be within the selected organism)\n"
|
| 96 |
-
"Example: Any nucleotide sequence between 100 and 10,000 base pairs.\n\n"
|
| 97 |
-
"*Note: to multiplex, separate multiple queries by new lines*\n"
|
| 98 |
-
"Example:\n"
|
| 99 |
-
"1,1,1000\n"
|
| 100 |
-
"5,1,500\n"
|
| 101 |
-
"etc.")
|
| 102 |
-
self.gene_entry_field.setPlaceholderText(placeholder_text)
|
| 103 |
-
|
| 104 |
-
def enable_view_targets(self, enable):
|
| 105 |
-
self.push_button_view_targets.setEnabled(enable)
|
| 106 |
-
|
| 107 |
-
def enable_generate_library(self, enable):
|
| 108 |
-
self.generate_library.setEnabled(enable)
|
| 109 |
-
|
| 110 |
-
def set_progress(self, value):
|
| 111 |
-
if self.progressBar:
|
| 112 |
-
self.progressBar.setValue(value)
|
| 113 |
-
|
| 114 |
-
def reset_progress(self):
|
| 115 |
-
if self.progressBar:
|
| 116 |
-
self.progressBar.setValue(0)
|
| 117 |
-
|
| 118 |
-
def toggle_annotation(self, gene_checked):
|
| 119 |
-
self.step2.setEnabled(True)
|
| 120 |
-
|
| 121 |
-
def update_endo_choice(self, endos):
|
| 122 |
-
self.endo_choice.clear()
|
| 123 |
-
self.endo_choice.addItems(endos)
|
| 124 |
-
|
| 125 |
-
def bring_to_front(self):
|
| 126 |
-
self.show()
|
| 127 |
-
self.setWindowState(self.windowState() & ~QtCore.Qt.WindowState.WindowMinimized | QtCore.Qt.WindowState.WindowActive)
|
| 128 |
-
self.raise_()
|
| 129 |
-
self.activateWindow()
|
| 130 |
-
QtWidgets.QApplication.setActiveWindow(self)
|
| 131 |
-
|
| 132 |
-
def on_change_directory(self):
|
| 133 |
-
# This method will be connected to the controller
|
| 134 |
-
pass
|
| 135 |
-
|
| 136 |
-
def update_theme_icon(self):
|
| 137 |
-
# Swap the icons: use dark_mode.png for light mode, and light_mode.png for dark mode
|
| 138 |
-
icon = QtGui.QIcon(os.path.join(self.settings.get_assets_dir(), "dark_mode.png" if self.settings.is_dark_mode() else "light_mode.png"))
|
| 139 |
-
self.theme_toggle_button.setIcon(icon)
|
| 140 |
-
self.theme_toggle_button.setIconSize(QtCore.QSize(24, 24))
|
| 141 |
-
|
| 142 |
-
# Update the entire application's theme
|
| 143 |
-
self.settings.apply_theme()
|
| 144 |
-
|
| 145 |
-
def resizeEvent(self, event):
|
| 146 |
-
super().resizeEvent(event)
|
| 147 |
-
# Reposition the theme toggle button when the window is resized
|
| 148 |
-
self.theme_toggle_button.setGeometry(self.width() - 40, 10, 32, 32)
|
| 149 |
-
|
| 150 |
-
def on_theme_toggle(self):
|
| 151 |
-
# This method will be connected to the controller
|
| 152 |
-
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,265 +0,0 @@
|
|
| 1 |
-
from PyQt6.QtWidgets import QMainWindow, QPushButton, QRadioButton, QComboBox, QPlainTextEdit, QProgressBar, QMenuBar, QMenu, QStackedWidget, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QFrame
|
| 2 |
-
from PyQt6.QtGui import QIcon, QAction, QFont
|
| 3 |
-
from PyQt6.QtCore import Qt, QPoint
|
| 4 |
-
from PyQt6 import uic, QtWidgets, QtCore
|
| 5 |
-
from utils.ui import scale_ui, show_error
|
| 6 |
-
import os
|
| 7 |
-
from typing import Optional
|
| 8 |
-
|
| 9 |
-
class MainWindowView(QMainWindow):
|
| 10 |
-
def __init__(self, global_settings):
|
| 11 |
-
super().__init__()
|
| 12 |
-
self.global_settings = global_settings
|
| 13 |
-
self._init_ui()
|
| 14 |
-
|
| 15 |
-
def _init_ui(self) -> None:
|
| 16 |
-
try:
|
| 17 |
-
# self._load_ui_file()
|
| 18 |
-
self._init_window_properties()
|
| 19 |
-
self._init_custom_title_bar()
|
| 20 |
-
self._init_ui_elements()
|
| 21 |
-
self._scale_ui()
|
| 22 |
-
except Exception as e:
|
| 23 |
-
self._handle_init_error(e)
|
| 24 |
-
|
| 25 |
-
def _load_ui_file(self) -> None:
|
| 26 |
-
ui_file = os.path.join(self.global_settings.get_ui_dir(), "home_window.ui")
|
| 27 |
-
uic.loadUi(ui_file, self)
|
| 28 |
-
|
| 29 |
-
def _init_window_properties(self) -> None:
|
| 30 |
-
self.setWindowFlags(Qt.WindowType.FramelessWindowHint)
|
| 31 |
-
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
|
| 32 |
-
|
| 33 |
-
def _init_ui_elements(self) -> None:
|
| 34 |
-
# Create a main widget to hold everything
|
| 35 |
-
main_widget = QWidget()
|
| 36 |
-
main_layout = QVBoxLayout(main_widget)
|
| 37 |
-
main_layout.setContentsMargins(0, 0, 0, 0)
|
| 38 |
-
main_layout.setSpacing(0)
|
| 39 |
-
|
| 40 |
-
# Add the custom title bar
|
| 41 |
-
main_layout.addWidget(self.title_bar)
|
| 42 |
-
|
| 43 |
-
# Create and add the divider
|
| 44 |
-
divider = QFrame()
|
| 45 |
-
divider.setFrameShape(QFrame.Shape.HLine)
|
| 46 |
-
divider.setFrameShadow(QFrame.Shadow.Sunken)
|
| 47 |
-
divider.setStyleSheet("background-color: #c0c0c0;") # Light gray color
|
| 48 |
-
divider.setFixedHeight(1) # 1 pixel height
|
| 49 |
-
main_layout.addWidget(divider)
|
| 50 |
-
|
| 51 |
-
# Create a widget to hold the original content
|
| 52 |
-
content_widget = QWidget()
|
| 53 |
-
content_layout = QVBoxLayout(content_widget)
|
| 54 |
-
|
| 55 |
-
# Move the existing widgets to the content layout
|
| 56 |
-
for child in self.children():
|
| 57 |
-
if isinstance(child, (QMenuBar, QWidget)) and child != self.title_bar:
|
| 58 |
-
content_layout.addWidget(child)
|
| 59 |
-
|
| 60 |
-
# Create the stacked widget
|
| 61 |
-
self.stacked_widget = QStackedWidget()
|
| 62 |
-
|
| 63 |
-
# Add the content widget and stacked widget to the main layout
|
| 64 |
-
main_layout.addWidget(content_widget)
|
| 65 |
-
main_layout.addWidget(self.stacked_widget)
|
| 66 |
-
|
| 67 |
-
# Set the main widget as the central widget
|
| 68 |
-
self.setCentralWidget(main_widget)
|
| 69 |
-
|
| 70 |
-
# Initialize other UI elements
|
| 71 |
-
self._init_menuBar()
|
| 72 |
-
self._init_grpNavigationMenu()
|
| 73 |
-
self._init_grpStep1()
|
| 74 |
-
self._init_grpStep2()
|
| 75 |
-
self._init_grpStep3()
|
| 76 |
-
|
| 77 |
-
def _init_custom_title_bar(self) -> None:
|
| 78 |
-
self.title_bar = QWidget(self)
|
| 79 |
-
self.title_bar.setObjectName("custom_title_bar")
|
| 80 |
-
self.title_bar.setFixedHeight(32) # Reduced height
|
| 81 |
-
|
| 82 |
-
# Create the main horizontal layout for the title bar
|
| 83 |
-
layout = QHBoxLayout(self.title_bar)
|
| 84 |
-
layout.setContentsMargins(10, 0, 10, 0) # Equal margins on left and right
|
| 85 |
-
layout.setSpacing(5) # Reduced spacing between items
|
| 86 |
-
|
| 87 |
-
# ----- Window Control Buttons -----
|
| 88 |
-
button_font = QFont("Arial", 8) # Smaller font size for button text
|
| 89 |
-
|
| 90 |
-
self.minimize_button = QPushButton("-", self.title_bar)
|
| 91 |
-
self.minimize_button.setObjectName("minimize_button")
|
| 92 |
-
self.minimize_button.setFixedSize(20, 20) # Reduced size from 24x24 to 20x20
|
| 93 |
-
self.minimize_button.setFont(button_font)
|
| 94 |
-
|
| 95 |
-
self.maximize_button = QPushButton("â›¶", self.title_bar)
|
| 96 |
-
self.maximize_button.setObjectName("maximize_button")
|
| 97 |
-
self.maximize_button.setFixedSize(20, 20) # Reduced size from 24x24 to 20x20
|
| 98 |
-
self.maximize_button.setFont(button_font)
|
| 99 |
-
|
| 100 |
-
self.close_button = QPushButton("✕", self.title_bar)
|
| 101 |
-
self.close_button.setObjectName("close_button")
|
| 102 |
-
self.close_button.setFixedSize(20, 20) # Reduced size from 24x24 to 20x20
|
| 103 |
-
self.close_button.setFont(button_font)
|
| 104 |
-
|
| 105 |
-
# Apply a style to center the text vertically and horizontally
|
| 106 |
-
button_style = """
|
| 107 |
-
QPushButton {
|
| 108 |
-
padding: 0px;
|
| 109 |
-
margin: 0px;
|
| 110 |
-
line-height: 20px;
|
| 111 |
-
text-align: center;
|
| 112 |
-
}
|
| 113 |
-
"""
|
| 114 |
-
self.minimize_button.setStyleSheet(button_style)
|
| 115 |
-
self.maximize_button.setStyleSheet(button_style)
|
| 116 |
-
self.close_button.setStyleSheet(button_style)
|
| 117 |
-
|
| 118 |
-
# ----- Left Widget (Minimize, Maximize, Close) -----
|
| 119 |
-
left_widget = QWidget()
|
| 120 |
-
left_layout = QHBoxLayout(left_widget)
|
| 121 |
-
left_layout.setContentsMargins(0, 0, 0, 0)
|
| 122 |
-
left_layout.setSpacing(5)
|
| 123 |
-
left_layout.addWidget(self.close_button)
|
| 124 |
-
left_layout.addWidget(self.minimize_button)
|
| 125 |
-
left_layout.addWidget(self.maximize_button)
|
| 126 |
-
|
| 127 |
-
# ----- Theme Toggle Button -----
|
| 128 |
-
self.theme_toggle_button = QPushButton(self.title_bar)
|
| 129 |
-
self.theme_toggle_button.setObjectName("theme_toggle_button")
|
| 130 |
-
self.theme_toggle_button.setFixedSize(20, 20) # Reduced size from 24x24 to 20x20
|
| 131 |
-
self.theme_toggle_button.setStyleSheet("border: none;")
|
| 132 |
-
self.update_theme_icon()
|
| 133 |
-
|
| 134 |
-
# ----- Right Widget (Theme Toggle + Stretch) -----
|
| 135 |
-
right_widget = QWidget()
|
| 136 |
-
right_layout = QHBoxLayout(right_widget)
|
| 137 |
-
right_layout.setContentsMargins(0, 0, 0, 0)
|
| 138 |
-
right_layout.setSpacing(5)
|
| 139 |
-
|
| 140 |
-
# Add a stretch to push the toggle button to the far right within right_widget
|
| 141 |
-
right_layout.addStretch()
|
| 142 |
-
right_layout.addWidget(self.theme_toggle_button)
|
| 143 |
-
|
| 144 |
-
# ----- Synchronize Widths of Left and Right Widgets -----
|
| 145 |
-
# Adjust left_widget to calculate its required width
|
| 146 |
-
left_widget.adjustSize()
|
| 147 |
-
left_width = left_widget.sizeHint().width()
|
| 148 |
-
|
| 149 |
-
# Set right_widget's fixed width to match left_widget's width
|
| 150 |
-
right_widget.setFixedWidth(left_width)
|
| 151 |
-
|
| 152 |
-
# ----- Title Label -----
|
| 153 |
-
self.title_label = QLabel("CASPER", self.title_bar)
|
| 154 |
-
self.title_label.setObjectName("title_label")
|
| 155 |
-
self.title_label.setFont(QFont("Arial", 10, QFont.Weight.Bold)) # Reduced font size
|
| 156 |
-
self.title_label.setAlignment(Qt.AlignmentFlag.AlignCenter) # Center the text in the label
|
| 157 |
-
self.title_label.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Preferred)
|
| 158 |
-
|
| 159 |
-
# ----- Add Widgets to the Main Title Bar Layout -----
|
| 160 |
-
layout.addWidget(left_widget) # Left side buttons
|
| 161 |
-
layout.addStretch(1) # Stretchable space
|
| 162 |
-
layout.addWidget(self.title_label) # Centered title
|
| 163 |
-
layout.addStretch(1) # Stretchable space
|
| 164 |
-
layout.addWidget(right_widget) # Right side buttons (theme toggle + stretch)
|
| 165 |
-
|
| 166 |
-
# Optional: Ensure the title_label is truly centered
|
| 167 |
-
self.title_label.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Preferred)
|
| 168 |
-
def _init_menuBar(self) -> None:
|
| 169 |
-
self.action_change_directory = self._find_widget("actChangeDirectory", QAction)
|
| 170 |
-
self.action_open_genome_browser = self._find_widget("actOpenGenomeBrowser", QAction)
|
| 171 |
-
self.action_open_repository = self._find_widget("actOpenRepository", QAction)
|
| 172 |
-
self.action_open_NCBI_BLAST = self._find_widget("actOpenNCBIBLAST", QAction)
|
| 173 |
-
self.action_open_NCBI = self._find_widget("actOpenNCBI", QAction)
|
| 174 |
-
|
| 175 |
-
def _init_grpNavigationMenu(self) -> None:
|
| 176 |
-
self.push_button_new_genome = self._find_widget("pbtnNewGenome", QPushButton)
|
| 177 |
-
self.push_button_new_endonuclease = self._find_widget("pbtnNewEndonuclease", QPushButton)
|
| 178 |
-
self.push_button_multitargeting_analysis = self._find_widget("pbtnMultitargetingAnalysis", QPushButton)
|
| 179 |
-
self.push_button_population_analysis = self._find_widget("pbtnPopulationAnalysis", QPushButton)
|
| 180 |
-
self.push_button_combine_files = self._find_widget("pbtnCombineFiles", QPushButton)
|
| 181 |
-
|
| 182 |
-
def _init_grpStep1(self) -> None:
|
| 183 |
-
self.combo_box_organism = self._find_widget("cmbOrganism", QComboBox)
|
| 184 |
-
self.combo_box_endonuclease = self._find_widget("cmbEndonuclease", QComboBox)
|
| 185 |
-
|
| 186 |
-
def _init_grpStep2(self) -> None:
|
| 187 |
-
self.push_button_ncbi_file_search = self._find_widget("pbtnNCBIFileSearch", QPushButton)
|
| 188 |
-
self.combo_box_local_annotation_files = self._find_widget("cmbLocalAnnotationFiles", QComboBox)
|
| 189 |
-
|
| 190 |
-
def _init_grpStep3(self) -> None:
|
| 191 |
-
self.radio_button_feature = self._find_widget("rbtnFeature", QRadioButton)
|
| 192 |
-
self.radio_button_position = self._find_widget("rbtnPosition", QRadioButton)
|
| 193 |
-
self.radio_button_sequence = self._find_widget("rbtnSequence", QRadioButton)
|
| 194 |
-
self.text_edit_gene_entry = self._find_widget("txtedGeneEntry", QPlainTextEdit)
|
| 195 |
-
self.push_button_find_targets = self._find_widget("pbtnFindTargets", QPushButton)
|
| 196 |
-
self.progress_bar_find_targets = self._find_widget("progBarFindTargets", QProgressBar)
|
| 197 |
-
self.push_button_view_targets = self._find_widget("pbtnViewTargets", QPushButton)
|
| 198 |
-
self.push_button_generate_library = self._find_widget("pbtnGenerateLibrary", QPushButton)
|
| 199 |
-
|
| 200 |
-
placeholder_text = ("Example Inputs: \n\n"
|
| 201 |
-
"Option 1: Feature (ID, Locus Tag, or Name)\n"
|
| 202 |
-
"Example: 854068/YOL086C/ADH1 for S. cerevisiae alcohol dehydrogenase 1\n\n"
|
| 203 |
-
"Option 2: Position (chromosome,start,stop)\n"
|
| 204 |
-
"Example: 1,1,1000 for targeting chromosome 1, base pairs 1 to 1000\n\n"
|
| 205 |
-
"Option 3: Sequence (must be within the selected organism)\n"
|
| 206 |
-
"Example: Any nucleotide sequence between 100 and 10,000 base pairs.\n\n"
|
| 207 |
-
"*Note: to multiplex, separate multiple queries by new lines*\n"
|
| 208 |
-
"Example:\n"
|
| 209 |
-
"1,1,1000\n"
|
| 210 |
-
"5,1,500\n"
|
| 211 |
-
"etc.")
|
| 212 |
-
self.text_edit_gene_entry.setPlaceholderText(placeholder_text)
|
| 213 |
-
|
| 214 |
-
def _find_widget(self, name: str, widget_type: type) -> Optional[QtWidgets.QWidget]:
|
| 215 |
-
widget = self.findChild(widget_type, name)
|
| 216 |
-
if widget is None:
|
| 217 |
-
self.global_settings.logger.warning(f"Widget '{name}' not found in UI file.")
|
| 218 |
-
return widget
|
| 219 |
-
|
| 220 |
-
def _scale_ui(self) -> None:
|
| 221 |
-
scale_ui(self, custom_scale_width=1000, custom_scale_height=350)
|
| 222 |
-
|
| 223 |
-
def _handle_init_error(self, e: Exception) -> None:
|
| 224 |
-
error_msg = f"Error initializing MainWindowView: {str(e)}"
|
| 225 |
-
self.global_settings.logger.error(error_msg, exc_info=True)
|
| 226 |
-
show_error(self.global_settings, "Initialization Error", error_msg)
|
| 227 |
-
raise
|
| 228 |
-
|
| 229 |
-
def update_combo_box_endonuclease(self, endonuclease: list) -> None:
|
| 230 |
-
self.combo_box_endonuclease.clear()
|
| 231 |
-
self.combo_box_endonuclease.addItems(endonuclease)
|
| 232 |
-
|
| 233 |
-
def update_combo_box_organism(self, organisms: list) -> None:
|
| 234 |
-
self.combo_box_organism.clear()
|
| 235 |
-
self.combo_box_organism.addItems(organisms)
|
| 236 |
-
|
| 237 |
-
def update_combo_box_annotation_files(self, annotation_files: list) -> None:
|
| 238 |
-
self.combo_box_local_annotation_files.clear()
|
| 239 |
-
self.combo_box_local_annotation_files.addItems(annotation_files)
|
| 240 |
-
|
| 241 |
-
def set_progress_bar(self, value: int) -> None:
|
| 242 |
-
self.progress_bar_find_targets.setValue(value)
|
| 243 |
-
|
| 244 |
-
def reset_progress_bar(self) -> None:
|
| 245 |
-
self.set_progress_bar(0)
|
| 246 |
-
|
| 247 |
-
def update_theme_icon(self) -> None:
|
| 248 |
-
icon_name = "dark_mode.png" if self.global_settings.is_dark_mode() else "light_mode.png"
|
| 249 |
-
icon_path = os.path.join(self.global_settings.get_assets_dir(), icon_name)
|
| 250 |
-
icon = QIcon(icon_path)
|
| 251 |
-
self.theme_toggle_button.setIcon(icon)
|
| 252 |
-
self.theme_toggle_button.setIconSize(QtCore.QSize(16, 16)) # Reduced icon size from 18x18 to 16x16
|
| 253 |
-
|
| 254 |
-
def mousePressEvent(self, event):
|
| 255 |
-
if event.button() == Qt.MouseButton.LeftButton and self.title_bar.underMouse():
|
| 256 |
-
self.drag_position = event.globalPosition().toPoint() - self.frameGeometry().topLeft()
|
| 257 |
-
event.accept()
|
| 258 |
-
|
| 259 |
-
def mouseMoveEvent(self, event):
|
| 260 |
-
if event.buttons() & Qt.MouseButton.LeftButton and self.drag_position:
|
| 261 |
-
self.move(event.globalPosition().toPoint() - self.drag_position)
|
| 262 |
-
event.accept()
|
| 263 |
-
|
| 264 |
-
def mouseReleaseEvent(self, event):
|
| 265 |
-
self.drag_position = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,6 +1,6 @@
|
|
| 1 |
from PyQt6.QtWidgets import (
|
| 2 |
QMainWindow, QPushButton, QWidget, QVBoxLayout,
|
| 3 |
-
QHBoxLayout, QLabel, QFrame,
|
| 4 |
)
|
| 5 |
from PyQt6.QtGui import QIcon, QAction
|
| 6 |
from PyQt6.QtCore import Qt
|
|
@@ -17,6 +17,7 @@ class MainWindowView(QMainWindow, LoggingMixin):
|
|
| 17 |
QMainWindow.__init__(self)
|
| 18 |
LoggingMixin.__init__(self)
|
| 19 |
self.settings = global_settings
|
|
|
|
| 20 |
self._init_ui()
|
| 21 |
self.oldPos = None
|
| 22 |
|
|
@@ -149,22 +150,76 @@ class MainWindowView(QMainWindow, LoggingMixin):
|
|
| 149 |
left_layout.addWidget(self.minimize_window_button)
|
| 150 |
left_layout.addWidget(self.maximize_window_button)
|
| 151 |
|
| 152 |
-
# -----
|
| 153 |
-
self.
|
| 154 |
-
self.
|
| 155 |
-
self.
|
| 156 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
self.update_theme_icon()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
|
| 159 |
-
# ----- Right Widget (
|
| 160 |
right_widget = QWidget()
|
| 161 |
right_layout = QHBoxLayout(right_widget)
|
| 162 |
right_layout.setContentsMargins(0, 0, 0, 0)
|
| 163 |
right_layout.setSpacing(5)
|
| 164 |
|
| 165 |
-
# Add a stretch to push the
|
| 166 |
right_layout.addStretch()
|
| 167 |
-
right_layout.addWidget(self.
|
|
|
|
| 168 |
|
| 169 |
# Adjust left_widget to calculate its required width
|
| 170 |
left_widget.adjustSize()
|
|
@@ -205,15 +260,38 @@ class MainWindowView(QMainWindow, LoggingMixin):
|
|
| 205 |
|
| 206 |
def update_theme_icon(self) -> None:
|
| 207 |
try:
|
|
|
|
| 208 |
icon_name = "dark_mode.png" if self.settings.get_theme() == "dark" else "light_mode.png"
|
| 209 |
icon_path = os.path.join(self.settings.get_assets_dir_path(), icon_name)
|
| 210 |
-
|
| 211 |
-
self.theme_toggle_button.setIcon(icon)
|
| 212 |
-
self.theme_toggle_button.setIconSize(QtCore.QSize(16, 16))
|
| 213 |
except Exception as e:
|
| 214 |
self.log_error("update_theme_icon", e)
|
| 215 |
show_error(self.settings, "Theme Error", "Failed to update theme icon")
|
| 216 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
def resizeEvent(self, event):
|
| 218 |
super().resizeEvent(event)
|
| 219 |
self.log_debug(f"Window resized. New size: {self.size()}")
|
|
@@ -234,7 +312,9 @@ class MainWindowView(QMainWindow, LoggingMixin):
|
|
| 234 |
"tab_border_color": "#444444",
|
| 235 |
"tab_selected_border_color": "#51b85e",
|
| 236 |
"tab_hover_bg_color": "#3b3b3b",
|
| 237 |
-
"divider_color": "#444444"
|
|
|
|
|
|
|
| 238 |
},
|
| 239 |
"light": {
|
| 240 |
"bg_color": "#f0f0f0",
|
|
@@ -250,7 +330,9 @@ class MainWindowView(QMainWindow, LoggingMixin):
|
|
| 250 |
"tab_border_color": "#c0c0c0",
|
| 251 |
"tab_selected_border_color": "#51b85e",
|
| 252 |
"tab_hover_bg_color": "#e0e0e0",
|
| 253 |
-
"divider_color": "#c0c0c0"
|
|
|
|
|
|
|
| 254 |
}
|
| 255 |
}
|
| 256 |
|
|
@@ -258,7 +340,7 @@ class MainWindowView(QMainWindow, LoggingMixin):
|
|
| 258 |
theme = themes["dark"] if current_theme == "dark" else themes["light"]
|
| 259 |
qdarktheme.setup_theme(current_theme)
|
| 260 |
|
| 261 |
-
#
|
| 262 |
self.setStyleSheet(f"""
|
| 263 |
QWidget {{ background-color: {theme['bg_color']}; color: {theme['fg_color']}; }}
|
| 264 |
QPushButton {{ background-color: {theme['button_bg_color']}; border: 1px solid {theme['button_border_color']}; }}
|
|
@@ -270,6 +352,31 @@ class MainWindowView(QMainWindow, LoggingMixin):
|
|
| 270 |
QMenu {{ background-color: {theme['menu_bg_color']}; }}
|
| 271 |
QMenu::item:selected {{ background-color: {theme['menu_item_hover_bg_color']}; }}
|
| 272 |
QFrame#custom_divider {{ border-bottom: 1px solid {theme['divider_color']}; }}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 273 |
""")
|
| 274 |
|
| 275 |
# Set the tab widget stylesheet
|
|
@@ -305,6 +412,50 @@ class MainWindowView(QMainWindow, LoggingMixin):
|
|
| 305 |
}}
|
| 306 |
""")
|
| 307 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 308 |
def mousePressEvent(self, event):
|
| 309 |
"""Handle mouse press events for window dragging"""
|
| 310 |
if event.button() == Qt.MouseButton.LeftButton:
|
|
|
|
| 1 |
from PyQt6.QtWidgets import (
|
| 2 |
QMainWindow, QPushButton, QWidget, QVBoxLayout,
|
| 3 |
+
QHBoxLayout, QLabel, QFrame, QMenu,
|
| 4 |
)
|
| 5 |
from PyQt6.QtGui import QIcon, QAction
|
| 6 |
from PyQt6.QtCore import Qt
|
|
|
|
| 17 |
QMainWindow.__init__(self)
|
| 18 |
LoggingMixin.__init__(self)
|
| 19 |
self.settings = global_settings
|
| 20 |
+
self.action_toggle_theme = QAction("Toggle Theme", self)
|
| 21 |
self._init_ui()
|
| 22 |
self.oldPos = None
|
| 23 |
|
|
|
|
| 150 |
left_layout.addWidget(self.minimize_window_button)
|
| 151 |
left_layout.addWidget(self.maximize_window_button)
|
| 152 |
|
| 153 |
+
# ----- Add Button with Dropdown -----
|
| 154 |
+
self.add_button = QPushButton(self.title_bar)
|
| 155 |
+
self.add_button.setObjectName("add_button")
|
| 156 |
+
self.add_button.setFixedSize(20, 20)
|
| 157 |
+
|
| 158 |
+
# Create the dropdown menu
|
| 159 |
+
self.add_menu = QMenu(self.add_button)
|
| 160 |
+
self.add_menu.setObjectName("add_menu")
|
| 161 |
+
|
| 162 |
+
# Add actions to the menu
|
| 163 |
+
self.action_new_genome = self.add_menu.addAction("New Genome")
|
| 164 |
+
self.action_new_endonuclease = self.add_menu.addAction("New Endonuclease")
|
| 165 |
+
|
| 166 |
+
# Set the menu for the button
|
| 167 |
+
self.add_button.setMenu(self.add_menu)
|
| 168 |
+
self.add_button.setStyleSheet("""
|
| 169 |
+
QPushButton {
|
| 170 |
+
padding: 0px;
|
| 171 |
+
margin: 0px;
|
| 172 |
+
text-align: center;
|
| 173 |
+
border: none;
|
| 174 |
+
}
|
| 175 |
+
QPushButton::menu-indicator {
|
| 176 |
+
width: 0px;
|
| 177 |
+
}
|
| 178 |
+
""")
|
| 179 |
+
|
| 180 |
+
# Initial icon will be set in update_plus_icon method
|
| 181 |
+
self.update_plus_icon()
|
| 182 |
+
|
| 183 |
+
# ----- Settings Button with Dropdown -----
|
| 184 |
+
self.settings_button = QPushButton(self.title_bar)
|
| 185 |
+
self.settings_button.setObjectName("settings_button")
|
| 186 |
+
self.settings_button.setFixedSize(20, 20)
|
| 187 |
+
|
| 188 |
+
# Create the settings dropdown menu
|
| 189 |
+
self.settings_menu = QMenu(self.settings_button)
|
| 190 |
+
self.settings_menu.setObjectName("settings_menu")
|
| 191 |
+
|
| 192 |
+
# Update the theme icon and add the action to the menu
|
| 193 |
self.update_theme_icon()
|
| 194 |
+
self.settings_menu.addAction(self.action_toggle_theme)
|
| 195 |
+
|
| 196 |
+
# Set the menu for the button
|
| 197 |
+
self.settings_button.setMenu(self.settings_menu)
|
| 198 |
+
self.settings_button.setStyleSheet("""
|
| 199 |
+
QPushButton {
|
| 200 |
+
padding: 0px;
|
| 201 |
+
margin: 0px;
|
| 202 |
+
text-align: center;
|
| 203 |
+
border: none;
|
| 204 |
+
}
|
| 205 |
+
QPushButton::menu-indicator {
|
| 206 |
+
width: 0px;
|
| 207 |
+
}
|
| 208 |
+
""")
|
| 209 |
+
|
| 210 |
+
# Initial icon will be set in update_settings_icon method
|
| 211 |
+
self.update_settings_icon()
|
| 212 |
|
| 213 |
+
# ----- Right Widget (Add Button + Settings Button + Stretch) -----
|
| 214 |
right_widget = QWidget()
|
| 215 |
right_layout = QHBoxLayout(right_widget)
|
| 216 |
right_layout.setContentsMargins(0, 0, 0, 0)
|
| 217 |
right_layout.setSpacing(5)
|
| 218 |
|
| 219 |
+
# Add a stretch to push the buttons to the far right within right_widget
|
| 220 |
right_layout.addStretch()
|
| 221 |
+
right_layout.addWidget(self.add_button)
|
| 222 |
+
right_layout.addWidget(self.settings_button)
|
| 223 |
|
| 224 |
# Adjust left_widget to calculate its required width
|
| 225 |
left_widget.adjustSize()
|
|
|
|
| 260 |
|
| 261 |
def update_theme_icon(self) -> None:
|
| 262 |
try:
|
| 263 |
+
# Update the theme action icon
|
| 264 |
icon_name = "dark_mode.png" if self.settings.get_theme() == "dark" else "light_mode.png"
|
| 265 |
icon_path = os.path.join(self.settings.get_assets_dir_path(), icon_name)
|
| 266 |
+
self.action_toggle_theme.setIcon(QIcon(icon_path))
|
|
|
|
|
|
|
| 267 |
except Exception as e:
|
| 268 |
self.log_error("update_theme_icon", e)
|
| 269 |
show_error(self.settings, "Theme Error", "Failed to update theme icon")
|
| 270 |
|
| 271 |
+
def update_plus_icon(self) -> None:
|
| 272 |
+
"""Update the plus icon based on current theme"""
|
| 273 |
+
try:
|
| 274 |
+
icon_name = "plus_white.png" if self.settings.get_theme() == "dark" else "plus_dark.png"
|
| 275 |
+
icon_path = os.path.join(self.settings.get_assets_dir_path(), icon_name)
|
| 276 |
+
icon = QIcon(icon_path)
|
| 277 |
+
self.add_button.setIcon(icon)
|
| 278 |
+
self.add_button.setIconSize(QtCore.QSize(14, 14))
|
| 279 |
+
except Exception as e:
|
| 280 |
+
self.log_error("update_plus_icon", e)
|
| 281 |
+
show_error(self.settings, "Theme Error", "Failed to update plus icon")
|
| 282 |
+
|
| 283 |
+
def update_settings_icon(self) -> None:
|
| 284 |
+
"""Update the settings icon based on current theme"""
|
| 285 |
+
try:
|
| 286 |
+
icon_name = "settings_light.png" if self.settings.get_theme() == "dark" else "settings_dark.png"
|
| 287 |
+
icon_path = os.path.join(self.settings.get_assets_dir_path(), icon_name)
|
| 288 |
+
icon = QIcon(icon_path)
|
| 289 |
+
self.settings_button.setIcon(icon)
|
| 290 |
+
self.settings_button.setIconSize(QtCore.QSize(16, 16))
|
| 291 |
+
except Exception as e:
|
| 292 |
+
self.log_error("update_settings_icon", e)
|
| 293 |
+
show_error(self.settings, "Theme Error", "Failed to update settings icon")
|
| 294 |
+
|
| 295 |
def resizeEvent(self, event):
|
| 296 |
super().resizeEvent(event)
|
| 297 |
self.log_debug(f"Window resized. New size: {self.size()}")
|
|
|
|
| 312 |
"tab_border_color": "#444444",
|
| 313 |
"tab_selected_border_color": "#51b85e",
|
| 314 |
"tab_hover_bg_color": "#3b3b3b",
|
| 315 |
+
"divider_color": "#444444",
|
| 316 |
+
"menu_text_color": "#ffffff",
|
| 317 |
+
"menu_hover_text_color": "#ffffff",
|
| 318 |
},
|
| 319 |
"light": {
|
| 320 |
"bg_color": "#f0f0f0",
|
|
|
|
| 330 |
"tab_border_color": "#c0c0c0",
|
| 331 |
"tab_selected_border_color": "#51b85e",
|
| 332 |
"tab_hover_bg_color": "#e0e0e0",
|
| 333 |
+
"divider_color": "#c0c0c0",
|
| 334 |
+
"menu_text_color": "#000000",
|
| 335 |
+
"menu_hover_text_color": "#000000",
|
| 336 |
}
|
| 337 |
}
|
| 338 |
|
|
|
|
| 340 |
theme = themes["dark"] if current_theme == "dark" else themes["light"]
|
| 341 |
qdarktheme.setup_theme(current_theme)
|
| 342 |
|
| 343 |
+
# Update the existing stylesheet with menu styling
|
| 344 |
self.setStyleSheet(f"""
|
| 345 |
QWidget {{ background-color: {theme['bg_color']}; color: {theme['fg_color']}; }}
|
| 346 |
QPushButton {{ background-color: {theme['button_bg_color']}; border: 1px solid {theme['button_border_color']}; }}
|
|
|
|
| 352 |
QMenu {{ background-color: {theme['menu_bg_color']}; }}
|
| 353 |
QMenu::item:selected {{ background-color: {theme['menu_item_hover_bg_color']}; }}
|
| 354 |
QFrame#custom_divider {{ border-bottom: 1px solid {theme['divider_color']}; }}
|
| 355 |
+
|
| 356 |
+
QPushButton#add_button {{
|
| 357 |
+
background-color: {theme['button_bg_color']};
|
| 358 |
+
color: {theme['fg_color']};
|
| 359 |
+
border: 1px solid {theme['button_border_color']};
|
| 360 |
+
padding: 0px;
|
| 361 |
+
font-size: 16px;
|
| 362 |
+
line-height: 20px;
|
| 363 |
+
}}
|
| 364 |
+
|
| 365 |
+
QPushButton#add_button:hover {{
|
| 366 |
+
background-color: {theme['button_hover_bg_color']};
|
| 367 |
+
}}
|
| 368 |
+
|
| 369 |
+
QMenu {{
|
| 370 |
+
background-color: {theme['menu_bg_color']};
|
| 371 |
+
color: {theme['menu_text_color']};
|
| 372 |
+
border: 1px solid {theme['button_border_color']};
|
| 373 |
+
padding: 5px;
|
| 374 |
+
}}
|
| 375 |
+
|
| 376 |
+
QMenu::item:selected {{
|
| 377 |
+
background-color: {theme['menu_item_hover_bg_color']};
|
| 378 |
+
color: {theme['menu_hover_text_color']};
|
| 379 |
+
}}
|
| 380 |
""")
|
| 381 |
|
| 382 |
# Set the tab widget stylesheet
|
|
|
|
| 412 |
}}
|
| 413 |
""")
|
| 414 |
|
| 415 |
+
# Update the add button styling in the theme
|
| 416 |
+
self.add_button.setStyleSheet(f"""
|
| 417 |
+
QPushButton {{
|
| 418 |
+
padding: 0px;
|
| 419 |
+
margin: 0px;
|
| 420 |
+
line-height: 0px;
|
| 421 |
+
text-align: center;
|
| 422 |
+
border: none;
|
| 423 |
+
font-size: 14px;
|
| 424 |
+
color: {theme['fg_color']};
|
| 425 |
+
}}
|
| 426 |
+
QPushButton:hover {{
|
| 427 |
+
background-color: {theme['button_hover_bg_color']};
|
| 428 |
+
}}
|
| 429 |
+
QPushButton::menu-indicator {{
|
| 430 |
+
width: 0px;
|
| 431 |
+
}}
|
| 432 |
+
""")
|
| 433 |
+
|
| 434 |
+
# Update the settings button styling
|
| 435 |
+
self.settings_button.setStyleSheet(f"""
|
| 436 |
+
QPushButton {{
|
| 437 |
+
padding: 0px;
|
| 438 |
+
margin: 0px;
|
| 439 |
+
line-height: 0px;
|
| 440 |
+
text-align: center;
|
| 441 |
+
border: none;
|
| 442 |
+
font-size: 14px;
|
| 443 |
+
color: {theme['fg_color']};
|
| 444 |
+
background-color: transparent;
|
| 445 |
+
}}
|
| 446 |
+
QPushButton:hover {{
|
| 447 |
+
background-color: {theme['button_hover_bg_color']};
|
| 448 |
+
}}
|
| 449 |
+
QPushButton::menu-indicator {{
|
| 450 |
+
width: 0px;
|
| 451 |
+
}}
|
| 452 |
+
""")
|
| 453 |
+
|
| 454 |
+
# Update icons
|
| 455 |
+
self.update_theme_icon()
|
| 456 |
+
self.update_plus_icon()
|
| 457 |
+
self.update_settings_icon()
|
| 458 |
+
|
| 459 |
def mousePressEvent(self, event):
|
| 460 |
"""Handle mouse press events for window dragging"""
|
| 461 |
if event.button() == Qt.MouseButton.LeftButton:
|
|
@@ -27,20 +27,34 @@ class MultitargetingWindowView(QtWidgets.QMainWindow):
|
|
| 27 |
self._init_grpSeedAnalysis()
|
| 28 |
self._init_grpGlobalAnalysis()
|
| 29 |
|
|
|
|
|
|
|
| 30 |
def _init_grpSelectOrganism(self):
|
| 31 |
self.combo_box_organism = self._find_widget('cmbOrganism', QtWidgets.QComboBox)
|
| 32 |
self.combo_box_endonuclease = self._find_widget('cmbEndonuclease', QtWidgets.QComboBox)
|
|
|
|
| 33 |
self.push_button_analyze = self._find_widget('pbtnAnalyze', QtWidgets.QPushButton)
|
| 34 |
self.check_box_select_all = self._find_widget('chkSelectAll', QtWidgets.QCheckBox)
|
| 35 |
-
self.tool_button_sql_settings = self._find_widget('tbtnSQLSettings', QtWidgets.QToolButton)
|
| 36 |
self.table_seeds = self._find_widget('tblSeeds', QtWidgets.QTableWidget)
|
| 37 |
|
|
|
|
| 38 |
self.table_seeds.setColumnCount(8)
|
| 39 |
-
self.table_seeds.setHorizontalHeaderLabels([
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
self.table_seeds.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
| 41 |
self.table_seeds.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
|
| 42 |
-
self.table_seeds.setSelectionMode(QAbstractItemView.SelectionMode.
|
| 43 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
|
| 45 |
def _init_grpSeedAnalysis(self):
|
| 46 |
# Get the tab widget
|
|
@@ -77,11 +91,10 @@ class MultitargetingWindowView(QtWidgets.QMainWindow):
|
|
| 77 |
self.scroll_chromosome.viewport().installEventFilter(self)
|
| 78 |
self.graphical_view_chromosome.viewport().installEventFilter(self)
|
| 79 |
|
| 80 |
-
# Dictionary to map canvases to chromosomes
|
| 81 |
self.canvas_chromosome_map = {}
|
| 82 |
|
| 83 |
def _init_grpGlobalAnalysis(self):
|
| 84 |
-
self.
|
| 85 |
|
| 86 |
self.tab_repeats_vs_seed = self._find_widget('tabRepeatsVsSeed', QtWidgets.QWidget)
|
| 87 |
self.plot_repeats_vs_seed = self._find_widget('plotRepeatsVsSeed', QtWidgets.QWidget)
|
|
@@ -96,7 +109,6 @@ class MultitargetingWindowView(QtWidgets.QMainWindow):
|
|
| 96 |
return widget
|
| 97 |
|
| 98 |
def update_seeds_table(self, data):
|
| 99 |
-
"""Update the seeds table with the provided data"""
|
| 100 |
self.table_seeds.setRowCount(len(data))
|
| 101 |
for row, row_data in enumerate(data):
|
| 102 |
# Unpack the data
|
|
@@ -155,18 +167,11 @@ class MultitargetingWindowView(QtWidgets.QMainWindow):
|
|
| 155 |
y1 = data['counts']
|
| 156 |
x = range(len(y1))
|
| 157 |
|
| 158 |
-
# Plot with larger markers and line width for better visibility
|
| 159 |
self.repeats_vs_seed_canvas.axes.plot(x, y1, linewidth=1.5, marker='.', markersize=3)
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
self.repeats_vs_seed_canvas.axes.
|
| 163 |
-
self.repeats_vs_seed_canvas.axes.
|
| 164 |
-
self.repeats_vs_seed_canvas.axes.set_title('Number of Repeats per Seed ID Number', fontsize=14)
|
| 165 |
-
|
| 166 |
-
# Set tick label size
|
| 167 |
-
self.repeats_vs_seed_canvas.axes.tick_params(axis='both', which='major', labelsize=10)
|
| 168 |
-
|
| 169 |
-
# Add grid for better readability
|
| 170 |
self.repeats_vs_seed_canvas.axes.grid(True, linestyle='--', alpha=0.7)
|
| 171 |
|
| 172 |
# Store statistics if needed
|
|
@@ -192,32 +197,19 @@ class MultitargetingWindowView(QtWidgets.QMainWindow):
|
|
| 192 |
self.sequences_vs_repeats_canvas.axes.clear()
|
| 193 |
|
| 194 |
if data and 'x_vals' in data and 'y_vals' in data:
|
| 195 |
-
x = data['x_vals']
|
| 196 |
-
y = data['y_vals']
|
| 197 |
-
|
| 198 |
-
# Create scatter plot with specific style
|
| 199 |
-
self.sequences_vs_repeats_canvas.axes.scatter(x, y, s=15, color='blue')
|
| 200 |
|
| 201 |
-
|
| 202 |
self.sequences_vs_repeats_canvas.axes.set_yscale('log')
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
self.sequences_vs_repeats_canvas.axes.
|
| 206 |
-
self.sequences_vs_repeats_canvas.axes.
|
| 207 |
-
self.sequences_vs_repeats_canvas.axes.set_title('Number of Sequences per Number of Repeats', fontsize=14)
|
| 208 |
-
|
| 209 |
-
# Set tick label size
|
| 210 |
-
self.sequences_vs_repeats_canvas.axes.tick_params(axis='both', which='major', labelsize=10)
|
| 211 |
-
|
| 212 |
-
# Add grid for better readability
|
| 213 |
self.sequences_vs_repeats_canvas.axes.grid(True, linestyle='--', alpha=0.7)
|
| 214 |
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
# Set axis ranges to match the image
|
| 219 |
-
self.sequences_vs_repeats_canvas.axes.set_xlim(0, max(x) + 5) # Add some padding
|
| 220 |
-
self.sequences_vs_repeats_canvas.axes.set_ylim(1, 10**4) # Log scale from 1 to 10^4
|
| 221 |
|
| 222 |
self.sequences_vs_repeats_canvas.draw()
|
| 223 |
|
|
@@ -274,7 +266,6 @@ class MultitargetingWindowView(QtWidgets.QMainWindow):
|
|
| 274 |
self.logger.error(f"Error updating repeat vs chromosome plot: {str(e)}")
|
| 275 |
|
| 276 |
def fill_chromosome_viewer(self, seed_data, event_data):
|
| 277 |
-
"""Fill the chromosome viewer with visualization"""
|
| 278 |
try:
|
| 279 |
# Clear out old widgets in layout
|
| 280 |
for i in reversed(range(self.chromosome_layout.count())):
|
|
@@ -362,6 +353,27 @@ class MultitargetingWindowView(QtWidgets.QMainWindow):
|
|
| 362 |
except Exception as e:
|
| 363 |
self.logger.error(f"Error in chromosome event handler: {str(e)}")
|
| 364 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 365 |
class MplCanvas(FigureCanvasQTAgg):
|
| 366 |
def __init__(self, parent=None, width=8, height=6, dpi=100):
|
| 367 |
fig = Figure(figsize=(width, height), dpi=dpi, tight_layout=True)
|
|
|
|
| 27 |
self._init_grpSeedAnalysis()
|
| 28 |
self._init_grpGlobalAnalysis()
|
| 29 |
|
| 30 |
+
self.push_button_export_selected_gRNAs = self._find_widget('pbtnExportSelectedgRNAs', QtWidgets.QPushButton)
|
| 31 |
+
|
| 32 |
def _init_grpSelectOrganism(self):
|
| 33 |
self.combo_box_organism = self._find_widget('cmbOrganism', QtWidgets.QComboBox)
|
| 34 |
self.combo_box_endonuclease = self._find_widget('cmbEndonuclease', QtWidgets.QComboBox)
|
| 35 |
+
self.line_edit_max_results = self._find_widget('ledMaxResults', QtWidgets.QLineEdit)
|
| 36 |
self.push_button_analyze = self._find_widget('pbtnAnalyze', QtWidgets.QPushButton)
|
| 37 |
self.check_box_select_all = self._find_widget('chkSelectAll', QtWidgets.QCheckBox)
|
|
|
|
| 38 |
self.table_seeds = self._find_widget('tblSeeds', QtWidgets.QTableWidget)
|
| 39 |
|
| 40 |
+
# Set up table columns
|
| 41 |
self.table_seeds.setColumnCount(8)
|
| 42 |
+
self.table_seeds.setHorizontalHeaderLabels([
|
| 43 |
+
"Seed", "Total Repeats", "Avg. Repeats/Scaffold",
|
| 44 |
+
"Consensus Sequence", "% Consensus", "Score", "PAM", "Strand"
|
| 45 |
+
])
|
| 46 |
+
|
| 47 |
+
# Set table properties
|
| 48 |
self.table_seeds.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
| 49 |
self.table_seeds.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
|
| 50 |
+
self.table_seeds.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
|
| 51 |
+
# Set minimum width for the table
|
| 52 |
+
self.table_seeds.setMinimumWidth(650)
|
| 53 |
+
|
| 54 |
+
# Add validation for max results line edit
|
| 55 |
+
self.line_edit_max_results.setValidator(QtGui.QIntValidator())
|
| 56 |
+
# Set default value
|
| 57 |
+
self.line_edit_max_results.setText("1000")
|
| 58 |
|
| 59 |
def _init_grpSeedAnalysis(self):
|
| 60 |
# Get the tab widget
|
|
|
|
| 91 |
self.scroll_chromosome.viewport().installEventFilter(self)
|
| 92 |
self.graphical_view_chromosome.viewport().installEventFilter(self)
|
| 93 |
|
|
|
|
| 94 |
self.canvas_chromosome_map = {}
|
| 95 |
|
| 96 |
def _init_grpGlobalAnalysis(self):
|
| 97 |
+
self.tab_statistics_overview = self._find_widget('tabStatisticsOverview', QtWidgets.QWidget)
|
| 98 |
|
| 99 |
self.tab_repeats_vs_seed = self._find_widget('tabRepeatsVsSeed', QtWidgets.QWidget)
|
| 100 |
self.plot_repeats_vs_seed = self._find_widget('plotRepeatsVsSeed', QtWidgets.QWidget)
|
|
|
|
| 109 |
return widget
|
| 110 |
|
| 111 |
def update_seeds_table(self, data):
|
|
|
|
| 112 |
self.table_seeds.setRowCount(len(data))
|
| 113 |
for row, row_data in enumerate(data):
|
| 114 |
# Unpack the data
|
|
|
|
| 167 |
y1 = data['counts']
|
| 168 |
x = range(len(y1))
|
| 169 |
|
|
|
|
| 170 |
self.repeats_vs_seed_canvas.axes.plot(x, y1, linewidth=1.5, marker='.', markersize=3)
|
| 171 |
+
self.repeats_vs_seed_canvas.axes.set_xlabel('Seed ID Number', fontsize=10)
|
| 172 |
+
self.repeats_vs_seed_canvas.axes.set_ylabel('Number of Repeats', fontsize=10)
|
| 173 |
+
self.repeats_vs_seed_canvas.axes.set_title('Number of Repeats per Seed ID Number', fontsize=10)
|
| 174 |
+
self.repeats_vs_seed_canvas.axes.tick_params(axis='both', which='major', labelsize=8)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
self.repeats_vs_seed_canvas.axes.grid(True, linestyle='--', alpha=0.7)
|
| 176 |
|
| 177 |
# Store statistics if needed
|
|
|
|
| 197 |
self.sequences_vs_repeats_canvas.axes.clear()
|
| 198 |
|
| 199 |
if data and 'x_vals' in data and 'y_vals' in data:
|
| 200 |
+
x = data['x_vals'] # Number of repeats
|
| 201 |
+
y = data['y_vals'] # Number of sequences
|
|
|
|
|
|
|
|
|
|
| 202 |
|
| 203 |
+
self.sequences_vs_repeats_canvas.axes.scatter(x, y, s=10)
|
| 204 |
self.sequences_vs_repeats_canvas.axes.set_yscale('log')
|
| 205 |
+
self.sequences_vs_repeats_canvas.axes.set_xlabel('Number of Repeats', fontsize=10)
|
| 206 |
+
self.sequences_vs_repeats_canvas.axes.set_ylabel('Number of Sequences', fontsize=10)
|
| 207 |
+
self.sequences_vs_repeats_canvas.axes.set_title('Number of Sequences per Number of Repeats', fontsize=10)
|
| 208 |
+
self.sequences_vs_repeats_canvas.axes.tick_params(axis='both', which='major', labelsize=8)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 209 |
self.sequences_vs_repeats_canvas.axes.grid(True, linestyle='--', alpha=0.7)
|
| 210 |
|
| 211 |
+
if x:
|
| 212 |
+
self.sequences_vs_repeats_canvas.axes.set_xlim(x[0] - 0.5, x[-1] + 0.5)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 213 |
|
| 214 |
self.sequences_vs_repeats_canvas.draw()
|
| 215 |
|
|
|
|
| 266 |
self.logger.error(f"Error updating repeat vs chromosome plot: {str(e)}")
|
| 267 |
|
| 268 |
def fill_chromosome_viewer(self, seed_data, event_data):
|
|
|
|
| 269 |
try:
|
| 270 |
# Clear out old widgets in layout
|
| 271 |
for i in reversed(range(self.chromosome_layout.count())):
|
|
|
|
| 353 |
except Exception as e:
|
| 354 |
self.logger.error(f"Error in chromosome event handler: {str(e)}")
|
| 355 |
|
| 356 |
+
def update_statistics_labels(self, total_repeats, avg_repeats, median_repeats, mode_repeats):
|
| 357 |
+
"""Update the statistics overview labels with new values"""
|
| 358 |
+
try:
|
| 359 |
+
# Find and update the statistics labels
|
| 360 |
+
total_label = self._find_widget('lblTotalRepeatsValue', QtWidgets.QLabel)
|
| 361 |
+
avg_label = self._find_widget('lblAverageRepeatsValue', QtWidgets.QLabel)
|
| 362 |
+
median_label = self._find_widget('lblMedianRepeatsValue', QtWidgets.QLabel)
|
| 363 |
+
mode_label = self._find_widget('lblModeRepeatsValue', QtWidgets.QLabel)
|
| 364 |
+
|
| 365 |
+
if total_label:
|
| 366 |
+
total_label.setText(str(round(float(total_repeats), 1)))
|
| 367 |
+
if avg_label:
|
| 368 |
+
avg_label.setText(str(round(float(avg_repeats), 1)))
|
| 369 |
+
if median_label:
|
| 370 |
+
median_label.setText(str(round(float(median_repeats), 1)))
|
| 371 |
+
if mode_label:
|
| 372 |
+
mode_label.setText(str(round(float(mode_repeats), 1)))
|
| 373 |
+
|
| 374 |
+
except Exception as e:
|
| 375 |
+
self.logger.error(f"Error updating statistics labels: {str(e)}")
|
| 376 |
+
|
| 377 |
class MplCanvas(FigureCanvasQTAgg):
|
| 378 |
def __init__(self, parent=None, width=8, height=6, dpi=100):
|
| 379 |
fig = Figure(figsize=(width, height), dpi=dpi, tight_layout=True)
|
|
@@ -18,7 +18,7 @@ class NCBIWindowView(QtWidgets.QMainWindow):
|
|
| 18 |
def _setup_basic_ui(self):
|
| 19 |
"""Initial minimal setup to show the window quickly"""
|
| 20 |
try:
|
| 21 |
-
uic.loadUi(os.path.join(self.settings.get_ui_dir_path(), "
|
| 22 |
|
| 23 |
QtCore.QTimer.singleShot(100, self._complete_initialization)
|
| 24 |
|
|
|
|
| 18 |
def _setup_basic_ui(self):
|
| 19 |
"""Initial minimal setup to show the window quickly"""
|
| 20 |
try:
|
| 21 |
+
uic.loadUi(os.path.join(self.settings.get_ui_dir_path(), "ncbi.ui"), self)
|
| 22 |
|
| 23 |
QtCore.QTimer.singleShot(100, self._complete_initialization)
|
| 24 |
|
|
@@ -2,14 +2,139 @@ from typing import Optional
|
|
| 2 |
from PyQt6 import QtWidgets, QtGui, QtCore, uic
|
| 3 |
from utils.ui import show_error
|
| 4 |
import os
|
|
|
|
| 5 |
|
| 6 |
class NewEndonucleaseView(QtWidgets.QMainWindow):
|
| 7 |
def __init__(self, settings):
|
| 8 |
super().__init__()
|
| 9 |
self.settings = settings
|
| 10 |
self.logger = self.settings.get_logger()
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
self.init_ui()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
def init_ui(self):
|
| 15 |
try:
|
|
|
|
| 2 |
from PyQt6 import QtWidgets, QtGui, QtCore, uic
|
| 3 |
from utils.ui import show_error
|
| 4 |
import os
|
| 5 |
+
import qdarktheme
|
| 6 |
|
| 7 |
class NewEndonucleaseView(QtWidgets.QMainWindow):
|
| 8 |
def __init__(self, settings):
|
| 9 |
super().__init__()
|
| 10 |
self.settings = settings
|
| 11 |
self.logger = self.settings.get_logger()
|
| 12 |
+
|
| 13 |
+
# Set window properties
|
| 14 |
+
self.setWindowTitle("New Endonuclease")
|
| 15 |
+
self.setMinimumSize(400, 500) # Set minimum window size
|
| 16 |
+
|
| 17 |
+
# Center the window on screen
|
| 18 |
+
screen = QtGui.QGuiApplication.primaryScreen()
|
| 19 |
+
screen_geometry = screen.geometry()
|
| 20 |
+
centerPoint = screen_geometry.center()
|
| 21 |
+
|
| 22 |
self.init_ui()
|
| 23 |
+
|
| 24 |
+
# Calculate and set position to center
|
| 25 |
+
frame_geometry = self.frameGeometry()
|
| 26 |
+
frame_geometry.moveCenter(centerPoint)
|
| 27 |
+
self.move(frame_geometry.topLeft())
|
| 28 |
+
|
| 29 |
+
# Apply theme
|
| 30 |
+
self.apply_theme()
|
| 31 |
+
|
| 32 |
+
def apply_theme(self):
|
| 33 |
+
current_theme = self.settings.get_theme()
|
| 34 |
+
themes = {
|
| 35 |
+
"dark": {
|
| 36 |
+
"bg_color": "#2b2b2b",
|
| 37 |
+
"fg_color": "#ffffff",
|
| 38 |
+
"button_bg_color": "#3a3a3a",
|
| 39 |
+
"button_border_color": "#5a5a5a",
|
| 40 |
+
"button_hover_bg_color": "#4a4a4a",
|
| 41 |
+
"input_bg_color": "#3a3a3a",
|
| 42 |
+
"input_border_color": "#5a5a5a",
|
| 43 |
+
"menu_bg_color": "#2b2b2b",
|
| 44 |
+
"menu_item_hover_bg_color": "#3a3a3a",
|
| 45 |
+
"divider_color": "#444444"
|
| 46 |
+
},
|
| 47 |
+
"light": {
|
| 48 |
+
"bg_color": "#f0f0f0",
|
| 49 |
+
"fg_color": "#000000",
|
| 50 |
+
"button_bg_color": "#e0e0e0",
|
| 51 |
+
"button_border_color": "#c0c0c0",
|
| 52 |
+
"button_hover_bg_color": "#d0d0d0",
|
| 53 |
+
"input_bg_color": "#ffffff",
|
| 54 |
+
"input_border_color": "#c0c0c0",
|
| 55 |
+
"menu_bg_color": "#f0f0f0",
|
| 56 |
+
"menu_item_hover_bg_color": "#e0e0e0",
|
| 57 |
+
"divider_color": "#c0c0c0"
|
| 58 |
+
}
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
theme = themes["dark"] if current_theme == "dark" else themes["light"]
|
| 62 |
+
qdarktheme.setup_theme(current_theme)
|
| 63 |
+
|
| 64 |
+
self.setStyleSheet(f"""
|
| 65 |
+
QMainWindow {{
|
| 66 |
+
background-color: {theme['bg_color']};
|
| 67 |
+
color: {theme['fg_color']};
|
| 68 |
+
}}
|
| 69 |
+
QWidget {{
|
| 70 |
+
background-color: {theme['bg_color']};
|
| 71 |
+
color: {theme['fg_color']};
|
| 72 |
+
}}
|
| 73 |
+
QPushButton {{
|
| 74 |
+
background-color: {theme['button_bg_color']};
|
| 75 |
+
border: 1px solid {theme['button_border_color']};
|
| 76 |
+
padding: 5px;
|
| 77 |
+
min-width: 80px;
|
| 78 |
+
}}
|
| 79 |
+
QPushButton:hover {{
|
| 80 |
+
background-color: {theme['button_hover_bg_color']};
|
| 81 |
+
}}
|
| 82 |
+
QLineEdit {{
|
| 83 |
+
background-color: {theme['input_bg_color']};
|
| 84 |
+
border: 1px solid {theme['input_border_color']};
|
| 85 |
+
padding: 5px;
|
| 86 |
+
}}
|
| 87 |
+
QComboBox {{
|
| 88 |
+
background-color: {theme['input_bg_color']};
|
| 89 |
+
border: 1px solid {theme['input_border_color']};
|
| 90 |
+
padding: 5px;
|
| 91 |
+
}}
|
| 92 |
+
QComboBox:hover {{
|
| 93 |
+
background-color: {theme['button_hover_bg_color']};
|
| 94 |
+
}}
|
| 95 |
+
QComboBox::drop-down {{
|
| 96 |
+
border: none;
|
| 97 |
+
}}
|
| 98 |
+
QComboBox::down-arrow {{
|
| 99 |
+
image: none;
|
| 100 |
+
border: none;
|
| 101 |
+
}}
|
| 102 |
+
QGroupBox {{
|
| 103 |
+
border: 1px solid {theme['button_border_color']};
|
| 104 |
+
margin-top: 1em;
|
| 105 |
+
padding-top: 0.5em;
|
| 106 |
+
}}
|
| 107 |
+
QGroupBox::title {{
|
| 108 |
+
subcontrol-origin: margin;
|
| 109 |
+
left: 10px;
|
| 110 |
+
padding: 0 3px 0 3px;
|
| 111 |
+
}}
|
| 112 |
+
QRadioButton {{
|
| 113 |
+
color: {theme['fg_color']};
|
| 114 |
+
}}
|
| 115 |
+
QRadioButton::indicator {{
|
| 116 |
+
width: 13px;
|
| 117 |
+
height: 13px;
|
| 118 |
+
}}
|
| 119 |
+
QRadioButton::indicator:checked {{
|
| 120 |
+
background-color: {theme['button_hover_bg_color']};
|
| 121 |
+
border: 2px solid {theme['button_border_color']};
|
| 122 |
+
border-radius: 7px;
|
| 123 |
+
}}
|
| 124 |
+
QRadioButton::indicator:unchecked {{
|
| 125 |
+
background-color: {theme['bg_color']};
|
| 126 |
+
border: 2px solid {theme['button_border_color']};
|
| 127 |
+
border-radius: 7px;
|
| 128 |
+
}}
|
| 129 |
+
QLabel {{
|
| 130 |
+
color: {theme['fg_color']};
|
| 131 |
+
}}
|
| 132 |
+
""")
|
| 133 |
+
|
| 134 |
+
def showEvent(self, event):
|
| 135 |
+
"""Override showEvent to apply theme when window is shown"""
|
| 136 |
+
super().showEvent(event)
|
| 137 |
+
self.apply_theme()
|
| 138 |
|
| 139 |
def init_ui(self):
|
| 140 |
try:
|
|
@@ -0,0 +1,138 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from PyQt6 import QtWidgets, uic, QtCore
|
| 2 |
+
from utils.ui import show_error
|
| 3 |
+
|
| 4 |
+
class OffTargetView(QtWidgets.QMainWindow):
|
| 5 |
+
def __init__(self, global_settings):
|
| 6 |
+
try:
|
| 7 |
+
super().__init__()
|
| 8 |
+
self.global_settings = global_settings
|
| 9 |
+
self.logger = global_settings.get_logger()
|
| 10 |
+
|
| 11 |
+
uic.loadUi(global_settings.get_ui_dir_path() + '/off_target.ui', self)
|
| 12 |
+
self.setWindowTitle("Off-Target Analysis")
|
| 13 |
+
|
| 14 |
+
self._init_ui_components()
|
| 15 |
+
|
| 16 |
+
self.apply_theme()
|
| 17 |
+
|
| 18 |
+
except Exception as e:
|
| 19 |
+
show_error(self.global_settings, "Error initializing OffTargetView", str(e))
|
| 20 |
+
|
| 21 |
+
def _init_ui_components(self):
|
| 22 |
+
"""Initialize UI components and set default values"""
|
| 23 |
+
try:
|
| 24 |
+
self._init_grpStep1()
|
| 25 |
+
self._init_grpStep2()
|
| 26 |
+
self._init_grpStep3()
|
| 27 |
+
|
| 28 |
+
self.push_button_cancel = self.findChild(QtWidgets.QPushButton, 'pbtnCancel')
|
| 29 |
+
self.push_button_submit = self.findChild(QtWidgets.QPushButton, 'pbtnSubmit')
|
| 30 |
+
|
| 31 |
+
self.setStyleSheet(self.global_settings.get_stylesheet())
|
| 32 |
+
|
| 33 |
+
except Exception as e:
|
| 34 |
+
show_error(self.global_settings, "Error initializing UI components", str(e))
|
| 35 |
+
|
| 36 |
+
def _init_grpStep1(self):
|
| 37 |
+
self.combo_box_organism = self.findChild(QtWidgets.QComboBox, 'cmbOrganism')
|
| 38 |
+
self.combo_box_endonuclease = self.findChild(QtWidgets.QComboBox, 'cmbEndonuclease')
|
| 39 |
+
|
| 40 |
+
def _init_grpStep2(self):
|
| 41 |
+
self.double_spin_box_tolerance = self.findChild(QtWidgets.QDoubleSpinBox, 'dspnTolerance')
|
| 42 |
+
self.combo_box_max_mismatches = self.findChild(QtWidgets.QComboBox, 'cmbMaxNoMismatches')
|
| 43 |
+
|
| 44 |
+
self.radio_button_average_output_yes = self.findChild(QtWidgets.QRadioButton, 'rbtnAverageOutputYes')
|
| 45 |
+
self.radio_button_average_output_no = self.findChild(QtWidgets.QRadioButton, 'rbtnAverageOutputNo')
|
| 46 |
+
|
| 47 |
+
self.radio_button_save_output_yes = self.findChild(QtWidgets.QRadioButton, 'rbtnSaveOutputFileYes')
|
| 48 |
+
self.radio_button_save_output_no = self.findChild(QtWidgets.QRadioButton, 'rbtnSaveOutputFileNo')
|
| 49 |
+
|
| 50 |
+
self.average_output_group = QtWidgets.QButtonGroup(self)
|
| 51 |
+
self.average_output_group.addButton(self.radio_button_average_output_yes)
|
| 52 |
+
self.average_output_group.addButton(self.radio_button_average_output_no)
|
| 53 |
+
|
| 54 |
+
self.save_output_group = QtWidgets.QButtonGroup(self)
|
| 55 |
+
self.save_output_group.addButton(self.radio_button_save_output_yes)
|
| 56 |
+
self.save_output_group.addButton(self.radio_button_save_output_no)
|
| 57 |
+
|
| 58 |
+
self.radio_button_average_output_no.setChecked(True)
|
| 59 |
+
self.radio_button_save_output_no.setChecked(True)
|
| 60 |
+
|
| 61 |
+
self.line_edit_output_file = self.findChild(QtWidgets.QLineEdit, 'ledSaveOutputFile')
|
| 62 |
+
self.radio_button_save_output_yes.toggled.connect(self._on_save_output_toggled)
|
| 63 |
+
self.line_edit_output_file.setEnabled(False) # Initially disabled
|
| 64 |
+
|
| 65 |
+
def _init_grpStep3(self):
|
| 66 |
+
"""Initialize progress bar only"""
|
| 67 |
+
self.prog_bar = self.findChild(QtWidgets.QProgressBar, 'progBar')
|
| 68 |
+
|
| 69 |
+
self.prog_bar.setMinimum(0)
|
| 70 |
+
self.prog_bar.setMaximum(100)
|
| 71 |
+
self.prog_bar.setValue(0)
|
| 72 |
+
|
| 73 |
+
def _on_save_output_toggled(self, checked):
|
| 74 |
+
self.line_edit_output_file.setEnabled(checked)
|
| 75 |
+
if not checked:
|
| 76 |
+
self.line_edit_output_file.clear()
|
| 77 |
+
|
| 78 |
+
def apply_theme(self):
|
| 79 |
+
"""Apply the current theme"""
|
| 80 |
+
if self.global_settings.get_theme() == "dark":
|
| 81 |
+
self.setStyleSheet(self.global_settings.get_dark_stylesheet())
|
| 82 |
+
else:
|
| 83 |
+
self.setStyleSheet(self.global_settings.get_light_stylesheet())
|
| 84 |
+
|
| 85 |
+
def get_analysis_parameters(self):
|
| 86 |
+
"""Get all parameters needed for off-target analysis"""
|
| 87 |
+
output_filename = self.line_edit_output_file.text().strip()
|
| 88 |
+
save_output = self.radio_button_save_output_yes.isChecked()
|
| 89 |
+
|
| 90 |
+
return {
|
| 91 |
+
'organism': self.combo_box_organism.currentText(),
|
| 92 |
+
'endonuclease': self.combo_box_endonuclease.currentText(),
|
| 93 |
+
'max_mismatches': int(self.combo_box_max_mismatches.currentText()),
|
| 94 |
+
'tolerance': self.double_spin_box_tolerance.value(),
|
| 95 |
+
'average_output': self.radio_button_average_output_yes.isChecked(),
|
| 96 |
+
'save_output': save_output,
|
| 97 |
+
'output_filename': output_filename if save_output else ''
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
def set_combo_box_organism(self, organisms):
|
| 101 |
+
self.combo_box_organism.clear()
|
| 102 |
+
self.combo_box_organism.addItems(organisms)
|
| 103 |
+
|
| 104 |
+
def set_combo_box_endonuclease(self, endonucleases):
|
| 105 |
+
self.combo_box_endonuclease.clear()
|
| 106 |
+
self.combo_box_endonuclease.addItems(endonucleases)
|
| 107 |
+
|
| 108 |
+
def set_combo_box_max_mismatches(self, max_mismatches=10):
|
| 109 |
+
self.combo_box_max_mismatches.clear()
|
| 110 |
+
self.combo_box_max_mismatches.addItems([str(mismatch) for mismatch in range(max_mismatches)])
|
| 111 |
+
self.combo_box_max_mismatches.setCurrentIndex(3)
|
| 112 |
+
|
| 113 |
+
def update_progress_bar(self, value, status=""):
|
| 114 |
+
self.prog_bar.setValue(value)
|
| 115 |
+
if status:
|
| 116 |
+
self.logger.debug(f"Progress: {status}")
|
| 117 |
+
|
| 118 |
+
def show_error(self, title, message):
|
| 119 |
+
QtWidgets.QMessageBox.critical(self, title, message)
|
| 120 |
+
|
| 121 |
+
def show_warning(self, title, message):
|
| 122 |
+
QtWidgets.QMessageBox.warning(self, title, message)
|
| 123 |
+
|
| 124 |
+
def closeEvent(self, event):
|
| 125 |
+
try:
|
| 126 |
+
self.push_button_cancel.clicked.emit()
|
| 127 |
+
event.accept()
|
| 128 |
+
except Exception as e:
|
| 129 |
+
self.logger.error(f"Error in closeEvent: {str(e)}")
|
| 130 |
+
event.accept()
|
| 131 |
+
|
| 132 |
+
def set_endonucleases(self, endonucleases):
|
| 133 |
+
"""Set available endonucleases in combo box"""
|
| 134 |
+
try:
|
| 135 |
+
self.combo_box_endonuclease.clear()
|
| 136 |
+
self.combo_box_endonuclease.addItems(endonucleases)
|
| 137 |
+
except Exception as e:
|
| 138 |
+
self.logger.error(f"Error setting endonucleases: {str(e)}")
|
|
@@ -2,16 +2,22 @@ from PyQt6 import QtWidgets, uic, QtGui, QtCore
|
|
| 2 |
from PyQt6.QtWidgets import QHeaderView, QAbstractItemView
|
| 3 |
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
|
| 4 |
from matplotlib.figure import Figure
|
|
|
|
| 5 |
import mplcursors
|
| 6 |
import numpy as np
|
| 7 |
import matplotlib.patches as patches
|
| 8 |
from utils.ui import show_error
|
|
|
|
| 9 |
|
| 10 |
class PopulationAnalysisWindowView(QtWidgets.QMainWindow):
|
| 11 |
def __init__(self, global_settings):
|
| 12 |
super().__init__()
|
| 13 |
self.settings = global_settings
|
| 14 |
self.logger = self.settings.get_logger()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
self.init_ui()
|
| 16 |
|
| 17 |
def init_ui(self):
|
|
@@ -24,26 +30,50 @@ class PopulationAnalysisWindowView(QtWidgets.QMainWindow):
|
|
| 24 |
def _init_ui_components(self):
|
| 25 |
self._init_grpSelectOrganisms()
|
| 26 |
self._init_grpSeedAnalysis()
|
| 27 |
-
|
|
|
|
| 28 |
|
| 29 |
def _init_grpSelectOrganisms(self):
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
|
| 48 |
def _init_grpSeedAnalysis(self):
|
| 49 |
self.line_edit_seed = self._find_widget('ledSeed', QtWidgets.QLineEdit)
|
|
@@ -76,15 +106,6 @@ class PopulationAnalysisWindowView(QtWidgets.QMainWindow):
|
|
| 76 |
self.table_locations.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents)
|
| 77 |
self.table_locations.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
|
| 78 |
|
| 79 |
-
# def _init_colormap(self):
|
| 80 |
-
# self.colormap_figure = self._find_widget('wgtColormap', QtWidgets.QWidget)
|
| 81 |
-
# if self.colormap_figure:
|
| 82 |
-
# self.colormap_layout = QtWidgets.QVBoxLayout()
|
| 83 |
-
# self.colormap_layout.setContentsMargins(0, 0, 0, 0)
|
| 84 |
-
# self.colormap_canvas = MplCanvas(self)
|
| 85 |
-
# self.colormap_layout.addWidget(self.colormap_canvas)
|
| 86 |
-
# self.colormap_figure.setLayout(self.colormap_layout)
|
| 87 |
-
|
| 88 |
def _find_widget(self, name: str, widget_type: type) -> QtWidgets.QWidget:
|
| 89 |
widget = self.findChild(widget_type, name)
|
| 90 |
if widget is None:
|
|
@@ -135,36 +156,98 @@ class PopulationAnalysisWindowView(QtWidgets.QMainWindow):
|
|
| 135 |
self.table_locations.resizeColumnsToContents()
|
| 136 |
|
| 137 |
def plot_heatmap(self, data, labels):
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
|
| 169 |
def clear_shared_seeds_table(self):
|
| 170 |
self.table_seed.setRowCount(0)
|
|
@@ -200,12 +283,47 @@ class PopulationAnalysisWindowView(QtWidgets.QMainWindow):
|
|
| 200 |
def sort_loc_finder_table(self, column):
|
| 201 |
self.loc_finder_table.sortItems(column)
|
| 202 |
|
| 203 |
-
|
| 204 |
-
|
| 205 |
try:
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 210 |
except Exception as e:
|
| 211 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
from PyQt6.QtWidgets import QHeaderView, QAbstractItemView
|
| 3 |
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
|
| 4 |
from matplotlib.figure import Figure
|
| 5 |
+
import matplotlib.pyplot as plt
|
| 6 |
import mplcursors
|
| 7 |
import numpy as np
|
| 8 |
import matplotlib.patches as patches
|
| 9 |
from utils.ui import show_error
|
| 10 |
+
import copy
|
| 11 |
|
| 12 |
class PopulationAnalysisWindowView(QtWidgets.QMainWindow):
|
| 13 |
def __init__(self, global_settings):
|
| 14 |
super().__init__()
|
| 15 |
self.settings = global_settings
|
| 16 |
self.logger = self.settings.get_logger()
|
| 17 |
+
|
| 18 |
+
# Connect to theme change signal
|
| 19 |
+
self.settings.theme_changed.connect(self._on_theme_changed)
|
| 20 |
+
|
| 21 |
self.init_ui()
|
| 22 |
|
| 23 |
def init_ui(self):
|
|
|
|
| 30 |
def _init_ui_components(self):
|
| 31 |
self._init_grpSelectOrganisms()
|
| 32 |
self._init_grpSeedAnalysis()
|
| 33 |
+
|
| 34 |
+
self.push_button_export_selected_gRNAs = self._find_widget('pbtnExportSelectedgRNAs', QtWidgets.QPushButton)
|
| 35 |
|
| 36 |
def _init_grpSelectOrganisms(self):
|
| 37 |
+
try:
|
| 38 |
+
self.logger.debug("Starting _init_grpSelectOrganisms")
|
| 39 |
+
|
| 40 |
+
self.combo_box_endonuclease = self._find_widget('cmbEndonuclease', QtWidgets.QComboBox)
|
| 41 |
+
self.table_organism = self._find_widget('tblOrganism', QtWidgets.QTableWidget)
|
| 42 |
+
self.push_button_analyze_organism = self._find_widget('pbtnAnalyzeOrganism', QtWidgets.QPushButton)
|
| 43 |
+
|
| 44 |
+
# Find the tab widget and heatmap widget
|
| 45 |
+
self.tab_widget_shared_seeds_heatmap = self._find_widget('tabsSharedSeedHeatmap', QtWidgets.QTabWidget)
|
| 46 |
+
self.tab_shared_seed_heatmap = self._find_widget('tabSharedSeedHeatmap', QtWidgets.QWidget)
|
| 47 |
+
self.heatmap_seed = self._find_widget('heatmapSeed', QtWidgets.QWidget)
|
| 48 |
+
|
| 49 |
+
self.logger.debug(f"Tab widget found: {self.tab_widget_shared_seeds_heatmap is not None}")
|
| 50 |
+
self.logger.debug(f"Heatmap widget found: {self.heatmap_seed is not None}")
|
| 51 |
+
|
| 52 |
+
# Create layout for heatmap
|
| 53 |
+
self.colormap_layout = QtWidgets.QVBoxLayout(self.heatmap_seed)
|
| 54 |
+
self.colormap_layout.setContentsMargins(0, 0, 0, 0)
|
| 55 |
+
|
| 56 |
+
# Create the matplotlib canvas
|
| 57 |
+
self.colormap_canvas = MplCanvas(self)
|
| 58 |
+
self.colormap_layout.addWidget(self.colormap_canvas)
|
| 59 |
+
|
| 60 |
+
# Set up the organism table
|
| 61 |
+
self.logger.debug("Setting up organism table")
|
| 62 |
+
self.table_organism.setColumnCount(1)
|
| 63 |
+
self.table_organism.setShowGrid(False)
|
| 64 |
+
self.table_organism.setHorizontalHeaderLabels(["Organism"])
|
| 65 |
+
self.table_organism.horizontalHeader().setSectionsClickable(True)
|
| 66 |
+
self.table_organism.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
|
| 67 |
+
self.table_organism.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
| 68 |
+
self.table_organism.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
|
| 69 |
+
self.table_organism.setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection)
|
| 70 |
+
|
| 71 |
+
self.logger.debug("Completed _init_grpSelectOrganisms")
|
| 72 |
+
|
| 73 |
+
except Exception as e:
|
| 74 |
+
self.logger.error(f"Error in _init_grpSelectOrganisms: {str(e)}")
|
| 75 |
+
self.logger.exception("Full traceback:")
|
| 76 |
+
show_error(self.settings, "Error initializing select organisms group", str(e))
|
| 77 |
|
| 78 |
def _init_grpSeedAnalysis(self):
|
| 79 |
self.line_edit_seed = self._find_widget('ledSeed', QtWidgets.QLineEdit)
|
|
|
|
| 106 |
self.table_locations.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents)
|
| 107 |
self.table_locations.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
|
| 108 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
def _find_widget(self, name: str, widget_type: type) -> QtWidgets.QWidget:
|
| 110 |
widget = self.findChild(widget_type, name)
|
| 111 |
if widget is None:
|
|
|
|
| 156 |
self.table_locations.resizeColumnsToContents()
|
| 157 |
|
| 158 |
def plot_heatmap(self, data, labels):
|
| 159 |
+
"""Plot heatmap of shared seeds between organisms"""
|
| 160 |
+
try:
|
| 161 |
+
self.logger.debug("Starting plot_heatmap")
|
| 162 |
+
self.logger.debug(f"Data shape: {np.array(data).shape}")
|
| 163 |
+
self.logger.debug(f"Data: {data}")
|
| 164 |
+
self.logger.debug(f"Labels: {labels}")
|
| 165 |
+
|
| 166 |
+
# Clear the previous plot and colorbar safely
|
| 167 |
+
self.colormap_canvas.axes.clear()
|
| 168 |
+
if hasattr(self.colormap_canvas, 'cbar'):
|
| 169 |
+
try:
|
| 170 |
+
self.colormap_canvas.cbar.remove()
|
| 171 |
+
except:
|
| 172 |
+
self.logger.debug("Could not remove old colorbar, creating new figure")
|
| 173 |
+
# If colorbar removal fails, create new figure and canvas
|
| 174 |
+
self.colormap_canvas.fig.clear()
|
| 175 |
+
self.colormap_canvas.axes = self.colormap_canvas.fig.add_subplot(111)
|
| 176 |
+
|
| 177 |
+
# Create a copy of data for labels
|
| 178 |
+
labels_data = copy.deepcopy(data)
|
| 179 |
+
|
| 180 |
+
# Set diagonal elements to 0 for visualization
|
| 181 |
+
for i in range(len(data)):
|
| 182 |
+
data[i][i] = 0
|
| 183 |
+
|
| 184 |
+
# Create the heatmap
|
| 185 |
+
self.logger.debug("Creating heatmap")
|
| 186 |
+
im = self.colormap_canvas.axes.imshow(data, cmap='summer')
|
| 187 |
+
|
| 188 |
+
# Add colorbar
|
| 189 |
+
self.logger.debug("Adding colorbar")
|
| 190 |
+
self.colormap_canvas.cbar = self.colormap_canvas.fig.colorbar(im, ax=self.colormap_canvas.axes)
|
| 191 |
+
|
| 192 |
+
# Determine text color based on theme
|
| 193 |
+
text_color = 'white' if self.settings.get_theme() == 'dark' else 'black'
|
| 194 |
+
|
| 195 |
+
self.colormap_canvas.cbar.ax.set_ylabel("", rotation=-90, va="bottom", fontsize=8, color=text_color)
|
| 196 |
+
self.colormap_canvas.cbar.ax.tick_params(colors=text_color)
|
| 197 |
+
|
| 198 |
+
# Add hover annotations
|
| 199 |
+
self.logger.debug("Setting up hover annotations")
|
| 200 |
+
cursor = mplcursors.cursor(im, hover=True)
|
| 201 |
+
@cursor.connect("add")
|
| 202 |
+
def on_add(sel):
|
| 203 |
+
sel.annotation.arrow_patch.set(arrowstyle="simple", fc="white", alpha=.5)
|
| 204 |
+
sel.annotation.set_bbox(None)
|
| 205 |
+
i, j = sel.target.index
|
| 206 |
+
# Show the actual number of shared seeds
|
| 207 |
+
sel.annotation.set_text(str(labels_data[i][j]))
|
| 208 |
+
|
| 209 |
+
# Set up axes
|
| 210 |
+
self.logger.debug("Setting up axes")
|
| 211 |
+
ax = self.colormap_canvas.axes
|
| 212 |
+
ax.set_xticks(np.arange(len(data)))
|
| 213 |
+
ax.set_yticks(np.arange(len(data)))
|
| 214 |
+
|
| 215 |
+
# Use numbers for both x-axis and y-axis
|
| 216 |
+
x_labels = [str(i+1) for i in range(len(data))]
|
| 217 |
+
y_labels = [str(i+1) for i in range(len(data))] # Removed "Organism" prefix
|
| 218 |
+
ax.set_xticklabels(x_labels, color=text_color)
|
| 219 |
+
ax.set_yticklabels(y_labels, color=text_color)
|
| 220 |
+
|
| 221 |
+
# Rotate labels
|
| 222 |
+
self.logger.debug("Rotating labels")
|
| 223 |
+
plt.setp(ax.get_xticklabels(), rotation=45, ha="right")
|
| 224 |
+
|
| 225 |
+
# Add grid
|
| 226 |
+
self.logger.debug("Adding grid")
|
| 227 |
+
for i in range(len(data)):
|
| 228 |
+
for j in range(len(data)):
|
| 229 |
+
ax.add_patch(patches.Rectangle(
|
| 230 |
+
(j - 0.5, i - 0.5), 1, 1,
|
| 231 |
+
fill=False, color="black", linewidth=1
|
| 232 |
+
))
|
| 233 |
+
|
| 234 |
+
ax.set_xlabel("Organism", fontsize=10, color=text_color)
|
| 235 |
+
ax.set_ylabel("Organism", fontsize=10, color=text_color)
|
| 236 |
+
ax.tick_params(axis='both', which='major', labelsize=8, colors=text_color)
|
| 237 |
+
|
| 238 |
+
# Adjust layout and draw
|
| 239 |
+
self.logger.debug("Adjusting layout")
|
| 240 |
+
self.colormap_canvas.fig.tight_layout()
|
| 241 |
+
|
| 242 |
+
self.logger.debug("Drawing canvas")
|
| 243 |
+
self.colormap_canvas.draw()
|
| 244 |
+
|
| 245 |
+
self.logger.debug("Completed plot_heatmap")
|
| 246 |
+
|
| 247 |
+
except Exception as e:
|
| 248 |
+
self.logger.error(f"Error plotting heatmap: {str(e)}")
|
| 249 |
+
self.logger.exception("Full traceback:")
|
| 250 |
+
show_error(self.settings, "Error plotting heatmap", str(e))
|
| 251 |
|
| 252 |
def clear_shared_seeds_table(self):
|
| 253 |
self.table_seed.setRowCount(0)
|
|
|
|
| 283 |
def sort_loc_finder_table(self, column):
|
| 284 |
self.loc_finder_table.sortItems(column)
|
| 285 |
|
| 286 |
+
def _on_theme_changed(self, theme):
|
| 287 |
+
"""Handle theme changes by updating the plot"""
|
| 288 |
try:
|
| 289 |
+
if hasattr(self, 'colormap_canvas') and hasattr(self.colormap_canvas, 'axes'):
|
| 290 |
+
text_color = 'white' if theme == 'dark' else 'black'
|
| 291 |
+
|
| 292 |
+
# Update axis labels
|
| 293 |
+
self.colormap_canvas.axes.xaxis.label.set_color(text_color)
|
| 294 |
+
self.colormap_canvas.axes.yaxis.label.set_color(text_color)
|
| 295 |
+
|
| 296 |
+
# Update tick labels
|
| 297 |
+
self.colormap_canvas.axes.tick_params(colors=text_color)
|
| 298 |
+
|
| 299 |
+
# Update colorbar if it exists
|
| 300 |
+
if hasattr(self.colormap_canvas, 'cbar'):
|
| 301 |
+
self.colormap_canvas.cbar.ax.set_ylabel("", rotation=-90, va="bottom", fontsize=8, color=text_color)
|
| 302 |
+
self.colormap_canvas.cbar.ax.tick_params(colors=text_color)
|
| 303 |
+
|
| 304 |
+
# Redraw the canvas
|
| 305 |
+
self.colormap_canvas.draw()
|
| 306 |
+
|
| 307 |
except Exception as e:
|
| 308 |
+
self.logger.error(f"Error updating plot theme: {str(e)}")
|
| 309 |
+
|
| 310 |
+
class MplCanvas(FigureCanvasQTAgg):
|
| 311 |
+
def __init__(self, parent=None, width=8, height=6, dpi=100):
|
| 312 |
+
self.fig = Figure(figsize=(width, height), dpi=dpi)
|
| 313 |
+
self.axes = self.fig.add_subplot(111)
|
| 314 |
+
super().__init__(self.fig)
|
| 315 |
+
|
| 316 |
+
# Set background colors based on current theme
|
| 317 |
+
self.update_colors(parent.settings.get_theme() if parent else 'light')
|
| 318 |
+
|
| 319 |
+
# Enable tight layout
|
| 320 |
+
self.fig.tight_layout()
|
| 321 |
+
|
| 322 |
+
def update_colors(self, theme):
|
| 323 |
+
"""Update figure and axes colors based on theme"""
|
| 324 |
+
if theme == 'dark':
|
| 325 |
+
self.fig.patch.set_facecolor('none')
|
| 326 |
+
self.axes.set_facecolor('#2d2d2d') # Dark background for plot area
|
| 327 |
+
else:
|
| 328 |
+
self.fig.patch.set_facecolor('none')
|
| 329 |
+
self.axes.set_facecolor('white')
|
|
@@ -13,7 +13,7 @@ class ViewTargetsView(QtWidgets.QMainWindow):
|
|
| 13 |
|
| 14 |
def __init__(self, global_settings):
|
| 15 |
super().__init__()
|
| 16 |
-
self.settings = global_settings
|
| 17 |
self.logger = self.settings.get_logger()
|
| 18 |
|
| 19 |
self.init_ui()
|
|
@@ -30,25 +30,42 @@ class ViewTargetsView(QtWidgets.QMainWindow):
|
|
| 30 |
self._init_grpGuideAnalysis()
|
| 31 |
self._init_grpGeneViewer()
|
| 32 |
|
| 33 |
-
self.
|
| 34 |
-
|
| 35 |
-
# Connect gene selection change with direct signal
|
| 36 |
-
self.combo_box_gene.currentTextChanged.connect(self._on_gene_changed)
|
| 37 |
|
| 38 |
def _init_grpGuideViewer(self):
|
| 39 |
self.combo_box_gene = self._find_widget('cmbGene', QtWidgets.QComboBox)
|
| 40 |
self.combo_box_endonuclease = self._find_widget('cmbEndonuclease', QtWidgets.QComboBox)
|
|
|
|
|
|
|
| 41 |
self.check_box_select_all = self._find_widget('chkSelectAll', QtWidgets.QCheckBox)
|
| 42 |
-
self.push_button_filter_options = self._find_widget('pbtnFilterOptions', QtWidgets.QPushButton)
|
| 43 |
self.push_button_scoring_options = self._find_widget('pbtnScoringOptions', QtWidgets.QPushButton)
|
| 44 |
-
self.
|
|
|
|
|
|
|
| 45 |
|
| 46 |
-
self.
|
| 47 |
-
self.
|
| 48 |
-
self.
|
| 49 |
-
self.
|
| 50 |
-
self.
|
| 51 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
|
| 53 |
def _init_grpGuideAnalysis(self):
|
| 54 |
self.push_button_off_target = self._find_widget('pbtnOffTarget', QtWidgets.QPushButton)
|
|
@@ -63,52 +80,71 @@ class ViewTargetsView(QtWidgets.QMainWindow):
|
|
| 63 |
self.text_edit_gene_viewer = self._find_widget('txtedGeneViewer', QtWidgets.QTextEdit)
|
| 64 |
self.push_button_reset_location = self._find_widget('pbtnResetLocation', QtWidgets.QPushButton)
|
| 65 |
|
|
|
|
|
|
|
| 66 |
def _find_widget(self, name: str, widget_type: type) -> Optional[QtWidgets.QWidget]:
|
| 67 |
widget = self.findChild(widget_type, name)
|
| 68 |
if widget is None:
|
| 69 |
self.logger.warning(f"Widget '{name}' not found in UI file.")
|
| 70 |
return widget
|
| 71 |
|
| 72 |
-
def
|
| 73 |
-
"""Ultra-fast
|
| 74 |
try:
|
| 75 |
-
|
|
|
|
| 76 |
|
| 77 |
-
# Store complete set of targets if not already stored
|
| 78 |
-
if not hasattr(self, '_complete_targets'):
|
| 79 |
-
self._complete_targets = targets
|
| 80 |
-
|
| 81 |
-
# Filter targets for currently selected gene
|
| 82 |
selected_text = self.combo_box_gene.currentText()
|
| 83 |
-
# Extract locus tag from "locus_tag: gene_name" format
|
| 84 |
-
selected_locus = selected_text.split(': ')[0] if ': ' in selected_text else selected_text
|
| 85 |
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
for
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
# Store filtered results
|
| 95 |
-
self._all_results = filtered_targets
|
| 96 |
else:
|
| 97 |
-
|
| 98 |
-
self._all_results = filtered_targets
|
| 99 |
|
| 100 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
|
| 102 |
# Completely freeze UI
|
| 103 |
self.setUpdatesEnabled(False)
|
| 104 |
-
self.
|
| 105 |
-
self.
|
| 106 |
-
self.
|
| 107 |
|
| 108 |
try:
|
| 109 |
-
#
|
| 110 |
-
self.
|
| 111 |
-
self.
|
| 112 |
|
| 113 |
# Get current headers to check for Azimuth column
|
| 114 |
headers = self.get_table_headers()
|
|
@@ -119,63 +155,80 @@ class ViewTargetsView(QtWidgets.QMainWindow):
|
|
| 119 |
|
| 120 |
# Load ALL rows at once
|
| 121 |
for row in range(total_rows):
|
| 122 |
-
|
| 123 |
|
| 124 |
-
#
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
target['sequence'], target['strand'], target['pam']
|
| 128 |
-
]):
|
| 129 |
-
item = QTableWidgetItem(str(value))
|
| 130 |
-
item.setFlags(flags)
|
| 131 |
-
self.table_targets.setItem(row, col, item)
|
| 132 |
|
| 133 |
-
#
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
|
| 142 |
-
#
|
| 143 |
-
|
| 144 |
-
self
|
|
|
|
|
|
|
|
|
|
| 145 |
|
| 146 |
# Add Azimuth score if column exists
|
| 147 |
-
if azimuth_index is not None and 'azimuth_score' in
|
| 148 |
-
azimuth_item = QTableWidgetItem()
|
| 149 |
-
|
| 150 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
|
| 152 |
-
# Set column widths
|
| 153 |
-
column_widths = [100, 100, 200, 80, 80, 80, 80, 100]
|
| 154 |
for col, width in enumerate(column_widths):
|
| 155 |
-
self.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
|
| 157 |
finally:
|
| 158 |
# Re-enable UI
|
| 159 |
-
self.
|
| 160 |
-
self.
|
| 161 |
self.setUpdatesEnabled(True)
|
| 162 |
-
self.
|
| 163 |
-
|
| 164 |
-
total_time = time.time() - start_time
|
| 165 |
-
self.logger.debug(f"Display time: {total_time:.2f} seconds for {total_rows} rows")
|
| 166 |
|
| 167 |
except Exception as e:
|
| 168 |
-
self.logger.error(f"Error in
|
| 169 |
-
show_error(self.settings, "Error displaying
|
| 170 |
|
| 171 |
def _handle_scroll_virtual(self, value, total_rows, row_height, buffer_rows):
|
| 172 |
-
"""Handle virtual scrolling with minimal updates"""
|
| 173 |
try:
|
| 174 |
-
if not hasattr(self, '
|
| 175 |
return
|
| 176 |
|
| 177 |
# Calculate visible range with safety checks
|
| 178 |
-
viewport_height = max(1, self.
|
| 179 |
row_height = max(1, row_height) # Ensure non-zero
|
| 180 |
visible_rows = viewport_height // row_height
|
| 181 |
|
|
@@ -186,31 +239,31 @@ class ViewTargetsView(QtWidgets.QMainWindow):
|
|
| 186 |
|
| 187 |
# Only update rows that aren't already loaded
|
| 188 |
for row in range(start_row, end_row):
|
| 189 |
-
if row < len(self.
|
| 190 |
-
|
| 191 |
|
| 192 |
# Create and set items efficiently
|
| 193 |
for col, value in enumerate([
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
]):
|
| 198 |
item = QTableWidgetItem(str(value))
|
| 199 |
item.setFlags(Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable)
|
| 200 |
-
self.
|
| 201 |
|
| 202 |
-
if not self.
|
| 203 |
details_button = QtWidgets.QPushButton("Details")
|
| 204 |
-
self.
|
| 205 |
|
| 206 |
except Exception as e:
|
| 207 |
self.logger.error(f"Error in _handle_scroll_virtual: {str(e)}")
|
| 208 |
|
| 209 |
-
def
|
| 210 |
-
"""Get selected
|
| 211 |
try:
|
| 212 |
-
selected_rows = set(
|
| 213 |
-
|
| 214 |
|
| 215 |
# Get column indices once
|
| 216 |
columns = {
|
|
@@ -219,54 +272,62 @@ class ViewTargetsView(QtWidgets.QMainWindow):
|
|
| 219 |
'sequence': 2,
|
| 220 |
'strand': 3,
|
| 221 |
'pam': 4,
|
| 222 |
-
'score': 5
|
|
|
|
| 223 |
}
|
| 224 |
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 241 |
|
| 242 |
-
if not
|
| 243 |
-
self.logger.warning("No valid
|
| 244 |
|
| 245 |
-
return
|
| 246 |
|
| 247 |
except Exception as e:
|
| 248 |
-
self.logger.error(f"Error getting selected
|
| 249 |
-
self.logger.error(f"Stack trace: {traceback.format_exc()}")
|
| 250 |
return []
|
| 251 |
|
| 252 |
def get_row_data(self, row):
|
| 253 |
return {
|
| 254 |
-
'location': self.
|
| 255 |
-
'endonuclease': self.
|
| 256 |
-
'sequence': self.
|
| 257 |
-
'strand': self.
|
| 258 |
-
'pam': self.
|
| 259 |
-
'score': self.
|
| 260 |
-
'off_target': self.
|
| 261 |
}
|
| 262 |
|
| 263 |
def set_combo_box_endonuclease(self, endonucleases):
|
| 264 |
self.combo_box_endonuclease.addItems(endonucleases)
|
| 265 |
|
| 266 |
def set_combo_box_gene(self, genes):
|
| 267 |
-
"""Set genes in combo box with optimized performance"""
|
| 268 |
try:
|
| 269 |
-
start_time = time.time()
|
| 270 |
|
| 271 |
# Disable UI updates
|
| 272 |
self.combo_box_gene.blockSignals(True)
|
|
@@ -293,9 +354,6 @@ class ViewTargetsView(QtWidgets.QMainWindow):
|
|
| 293 |
self.combo_box_gene.setUpdatesEnabled(True)
|
| 294 |
self.combo_box_gene.blockSignals(False)
|
| 295 |
|
| 296 |
-
total_time = time.time() - start_time
|
| 297 |
-
self.logger.debug(f"Combo box update time: {total_time:.2f} seconds")
|
| 298 |
-
|
| 299 |
except Exception as e:
|
| 300 |
self.logger.error(f"Error setting genes in combo box: {str(e)}")
|
| 301 |
self.logger.error(f"Stack trace: {traceback.format_exc()}")
|
|
@@ -322,33 +380,9 @@ class ViewTargetsView(QtWidgets.QMainWindow):
|
|
| 322 |
doc.setHtml(sequence)
|
| 323 |
self.text_edit_gene_viewer.setDocument(doc)
|
| 324 |
|
| 325 |
-
def
|
| 326 |
-
for row in range(self.
|
| 327 |
-
self.
|
| 328 |
-
|
| 329 |
-
def show_filter_options_dialog(self, options):
|
| 330 |
-
# Implement this method to show filter options dialog
|
| 331 |
-
pass
|
| 332 |
-
|
| 333 |
-
def filter_options_accepted(self):
|
| 334 |
-
# Implement this method to check if filter options were accepted
|
| 335 |
-
return True
|
| 336 |
-
|
| 337 |
-
def get_filter_options(self):
|
| 338 |
-
# Implement this method to return new filter options
|
| 339 |
-
return {}
|
| 340 |
-
|
| 341 |
-
def show_scoring_options_dialog(self, options):
|
| 342 |
-
# Implement this method to show scoring options dialog
|
| 343 |
-
pass
|
| 344 |
-
|
| 345 |
-
def scoring_options_accepted(self):
|
| 346 |
-
# Implement this method to check if scoring options were accepted
|
| 347 |
-
return True
|
| 348 |
-
|
| 349 |
-
def get_scoring_options(self):
|
| 350 |
-
# Implement this method to return new scoring options
|
| 351 |
-
return {}
|
| 352 |
|
| 353 |
def get_export_file_path(self):
|
| 354 |
# Implement this method to get the export file path from the user
|
|
@@ -360,11 +394,11 @@ class ViewTargetsView(QtWidgets.QMainWindow):
|
|
| 360 |
self.logger.debug(f"Gene selection changed to: {selected_text}")
|
| 361 |
|
| 362 |
# Reset scroll position
|
| 363 |
-
self.
|
| 364 |
|
| 365 |
# Filter and display targets
|
| 366 |
if hasattr(self, '_complete_targets'):
|
| 367 |
-
self.
|
| 368 |
|
| 369 |
# Emit signal for controller to update gene sequence
|
| 370 |
self.gene_selected.emit(selected_text)
|
|
@@ -376,30 +410,85 @@ class ViewTargetsView(QtWidgets.QMainWindow):
|
|
| 376 |
def get_table_headers(self):
|
| 377 |
"""Get current table headers"""
|
| 378 |
headers = []
|
| 379 |
-
for i in range(self.
|
| 380 |
-
headers.append(self.
|
| 381 |
return headers
|
| 382 |
|
| 383 |
def add_scoring_column(self, algorithm_name, position=None):
|
| 384 |
"""Add a new column for alternative scoring method at specified position"""
|
| 385 |
if position is None:
|
| 386 |
# Add to end if no position specified
|
| 387 |
-
position = self.
|
| 388 |
|
| 389 |
-
self.
|
| 390 |
-
self.
|
| 391 |
position,
|
| 392 |
QtWidgets.QTableWidgetItem(algorithm_name)
|
| 393 |
)
|
| 394 |
|
| 395 |
# Shift any existing columns after the insertion point
|
| 396 |
-
for i in range(self.
|
| 397 |
-
for row in range(self.
|
| 398 |
-
self.
|
| 399 |
|
| 400 |
# Move column header
|
| 401 |
-
header_item = self.
|
| 402 |
if header_item:
|
| 403 |
-
self.
|
| 404 |
|
| 405 |
return position
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
def __init__(self, global_settings):
|
| 15 |
super().__init__()
|
| 16 |
+
self.settings = global_settings
|
| 17 |
self.logger = self.settings.get_logger()
|
| 18 |
|
| 19 |
self.init_ui()
|
|
|
|
| 30 |
self._init_grpGuideAnalysis()
|
| 31 |
self._init_grpGeneViewer()
|
| 32 |
|
| 33 |
+
self.push_button_export_selected_grnas = self._find_widget('pbtnExportSelectedgRNAs', QtWidgets.QPushButton)
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
def _init_grpGuideViewer(self):
|
| 36 |
self.combo_box_gene = self._find_widget('cmbGene', QtWidgets.QComboBox)
|
| 37 |
self.combo_box_endonuclease = self._find_widget('cmbEndonuclease', QtWidgets.QComboBox)
|
| 38 |
+
self.check_box_filter_5_prime_g_sequences = self._find_widget('chkFilter5PrimeG', QtWidgets.QCheckBox)
|
| 39 |
+
self.spin_box_minimum_on_target_score = self._find_widget('spnMinOTScore', QtWidgets.QSpinBox)
|
| 40 |
self.check_box_select_all = self._find_widget('chkSelectAll', QtWidgets.QCheckBox)
|
|
|
|
| 41 |
self.push_button_scoring_options = self._find_widget('pbtnScoringOptions', QtWidgets.QPushButton)
|
| 42 |
+
self.table_guides = self._find_widget('tblGuides', QtWidgets.QTableWidget)
|
| 43 |
+
|
| 44 |
+
self.combo_box_gene.currentTextChanged.connect(self._on_gene_changed)
|
| 45 |
|
| 46 |
+
self.table_guides.setColumnCount(8)
|
| 47 |
+
self.table_guides.setHorizontalHeaderLabels(["Location", "Endonuclease", "Sequence", "Strand", "PAM", "Score", "Off-Target", "Details"])
|
| 48 |
+
self.table_guides.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
| 49 |
+
self.table_guides.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
|
| 50 |
+
self.table_guides.setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection)
|
| 51 |
+
|
| 52 |
+
# Enable horizontal scrolling
|
| 53 |
+
self.table_guides.setHorizontalScrollMode(QtWidgets.QAbstractItemView.ScrollMode.ScrollPerPixel)
|
| 54 |
+
self.table_guides.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
| 55 |
+
|
| 56 |
+
# Set size policy to allow table to shrink and expand
|
| 57 |
+
self.table_guides.setSizePolicy(
|
| 58 |
+
QtWidgets.QSizePolicy.Policy.Expanding,
|
| 59 |
+
QtWidgets.QSizePolicy.Policy.Expanding
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
# Set resize mode for header
|
| 63 |
+
header = self.table_guides.horizontalHeader()
|
| 64 |
+
header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeMode.Interactive)
|
| 65 |
+
header.setStretchLastSection(False) # Don't stretch the last section
|
| 66 |
+
|
| 67 |
+
# Set minimum section size to prevent columns from becoming too narrow
|
| 68 |
+
header.setMinimumSectionSize(80)
|
| 69 |
|
| 70 |
def _init_grpGuideAnalysis(self):
|
| 71 |
self.push_button_off_target = self._find_widget('pbtnOffTarget', QtWidgets.QPushButton)
|
|
|
|
| 80 |
self.text_edit_gene_viewer = self._find_widget('txtedGeneViewer', QtWidgets.QTextEdit)
|
| 81 |
self.push_button_reset_location = self._find_widget('pbtnResetLocation', QtWidgets.QPushButton)
|
| 82 |
|
| 83 |
+
self.text_edit_gene_viewer.setReadOnly(True)
|
| 84 |
+
|
| 85 |
def _find_widget(self, name: str, widget_type: type) -> Optional[QtWidgets.QWidget]:
|
| 86 |
widget = self.findChild(widget_type, name)
|
| 87 |
if widget is None:
|
| 88 |
self.logger.warning(f"Widget '{name}' not found in UI file.")
|
| 89 |
return widget
|
| 90 |
|
| 91 |
+
def display_guides_in_table(self, guides):
|
| 92 |
+
"""Ultra-fast guide display with virtual table and minimal UI updates"""
|
| 93 |
try:
|
| 94 |
+
# Store complete set of guides
|
| 95 |
+
self._all_guides = guides
|
| 96 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
selected_text = self.combo_box_gene.currentText()
|
|
|
|
|
|
|
| 98 |
|
| 99 |
+
# First filter by position/feature
|
| 100 |
+
if selected_text and "chrom" in selected_text and "start:" in selected_text:
|
| 101 |
+
filtered_guides = []
|
| 102 |
+
for guide in self._all_guides:
|
| 103 |
+
if guide.get('feature_id') == selected_text:
|
| 104 |
+
filtered_guides.append(guide)
|
| 105 |
+
self.logger.debug(f"Filtered to {len(filtered_guides)} guides for position {selected_text}")
|
|
|
|
|
|
|
|
|
|
| 106 |
else:
|
| 107 |
+
selected_locus = selected_text.split(': ')[0] if ': ' in selected_text else selected_text
|
|
|
|
| 108 |
|
| 109 |
+
if selected_locus:
|
| 110 |
+
filtered_guides = []
|
| 111 |
+
for guide in self._all_guides:
|
| 112 |
+
guide_locus = str(guide.get('feature_id', '')).strip()
|
| 113 |
+
if guide_locus.lower() == selected_locus.lower():
|
| 114 |
+
filtered_guides.append(guide)
|
| 115 |
+
else:
|
| 116 |
+
filtered_guides = self._all_guides
|
| 117 |
+
|
| 118 |
+
# Apply additional filters
|
| 119 |
+
final_guides = []
|
| 120 |
+
for guide in filtered_guides:
|
| 121 |
+
# Filter by minimum score
|
| 122 |
+
min_score = self.spin_box_minimum_on_target_score.value()
|
| 123 |
+
if float(guide.get('score', 0)) < min_score:
|
| 124 |
+
continue
|
| 125 |
+
|
| 126 |
+
# Filter by 5' G sequences
|
| 127 |
+
if self.check_box_filter_5_prime_g_sequences.isChecked():
|
| 128 |
+
sequence = guide.get('sequence', '')
|
| 129 |
+
if not sequence or not sequence.startswith('G'):
|
| 130 |
+
continue
|
| 131 |
+
|
| 132 |
+
final_guides.append(guide)
|
| 133 |
+
|
| 134 |
+
# Update table with new guides
|
| 135 |
+
total_rows = len(final_guides)
|
| 136 |
+
self.logger.debug(f"Processing {total_rows} rows for display after filtering")
|
| 137 |
|
| 138 |
# Completely freeze UI
|
| 139 |
self.setUpdatesEnabled(False)
|
| 140 |
+
self.table_guides.setUpdatesEnabled(False)
|
| 141 |
+
self.table_guides.setSortingEnabled(False)
|
| 142 |
+
self.table_guides.setVisible(False)
|
| 143 |
|
| 144 |
try:
|
| 145 |
+
# Clear and resize table
|
| 146 |
+
self.table_guides.clearContents()
|
| 147 |
+
self.table_guides.setRowCount(total_rows)
|
| 148 |
|
| 149 |
# Get current headers to check for Azimuth column
|
| 150 |
headers = self.get_table_headers()
|
|
|
|
| 155 |
|
| 156 |
# Load ALL rows at once
|
| 157 |
for row in range(total_rows):
|
| 158 |
+
guide = final_guides[row]
|
| 159 |
|
| 160 |
+
# Extract start position from location (format: "start-end")
|
| 161 |
+
location = guide['location']
|
| 162 |
+
start_pos = location.split('-')[0] if '-' in location else location
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
|
| 164 |
+
# Create and set basic items
|
| 165 |
+
items = [
|
| 166 |
+
(0, QTableWidgetItem(start_pos)), # Only show start position
|
| 167 |
+
(1, QTableWidgetItem(guide['endonuclease'])),
|
| 168 |
+
(2, QTableWidgetItem(guide['sequence'])),
|
| 169 |
+
(3, QTableWidgetItem(guide['strand'])),
|
| 170 |
+
(4, QTableWidgetItem(guide['pam'])),
|
| 171 |
+
(5, QTableWidgetItem(str(guide['score']))),
|
| 172 |
+
(6, QTableWidgetItem("--.--")) # Off-target placeholder
|
| 173 |
+
]
|
| 174 |
|
| 175 |
+
for col, item in items:
|
| 176 |
+
item.setFlags(flags)
|
| 177 |
+
self.table_guides.setItem(row, col, item)
|
| 178 |
|
| 179 |
+
# Only add details button if sequence has off-target details
|
| 180 |
+
sequence = guide['sequence']
|
| 181 |
+
if hasattr(self, '_off_target_details') and sequence in self._off_target_details:
|
| 182 |
+
details_button = QtWidgets.QPushButton("Details")
|
| 183 |
+
details_button.clicked.connect(self._show_details)
|
| 184 |
+
self.table_guides.setCellWidget(row, 7, details_button)
|
| 185 |
|
| 186 |
# Add Azimuth score if column exists
|
| 187 |
+
if azimuth_index is not None and 'azimuth_score' in guide:
|
| 188 |
+
azimuth_item = QTableWidgetItem(str(guide.get('azimuth_score', 0)))
|
| 189 |
+
self.table_guides.setItem(row, azimuth_index, azimuth_item)
|
| 190 |
+
|
| 191 |
+
# Updated column widths
|
| 192 |
+
column_widths = [
|
| 193 |
+
80, # Location
|
| 194 |
+
100, # Endonuclease
|
| 195 |
+
200, # Sequence
|
| 196 |
+
10, # Strand
|
| 197 |
+
80, # PAM
|
| 198 |
+
10, # Score
|
| 199 |
+
30, # Off-Target
|
| 200 |
+
80 # Details
|
| 201 |
+
]
|
| 202 |
|
| 203 |
+
# Set the column widths
|
|
|
|
| 204 |
for col, width in enumerate(column_widths):
|
| 205 |
+
self.table_guides.setColumnWidth(col, width)
|
| 206 |
+
|
| 207 |
+
essential_columns_width = sum(column_widths[:8]) # First 6 columns
|
| 208 |
+
self.table_guides.setMinimumWidth(essential_columns_width)
|
| 209 |
+
|
| 210 |
+
# Update the group box to properly handle scrolling
|
| 211 |
+
guide_viewer_group = self.findChild(QtWidgets.QGroupBox, 'grpGuideViewer')
|
| 212 |
+
guide_viewer_group.setMinimumWidth(essential_columns_width + 50) # Add some padding for scrollbar
|
| 213 |
|
| 214 |
finally:
|
| 215 |
# Re-enable UI
|
| 216 |
+
self.table_guides.setVisible(True)
|
| 217 |
+
self.table_guides.setUpdatesEnabled(True)
|
| 218 |
self.setUpdatesEnabled(True)
|
| 219 |
+
self.table_guides.setSortingEnabled(True)
|
|
|
|
|
|
|
|
|
|
| 220 |
|
| 221 |
except Exception as e:
|
| 222 |
+
self.logger.error(f"Error in display_guides: {str(e)}")
|
| 223 |
+
show_error(self.settings, "Error displaying guides", str(e))
|
| 224 |
|
| 225 |
def _handle_scroll_virtual(self, value, total_rows, row_height, buffer_rows):
|
|
|
|
| 226 |
try:
|
| 227 |
+
if not hasattr(self, '_all_guides') or not self._all_guides:
|
| 228 |
return
|
| 229 |
|
| 230 |
# Calculate visible range with safety checks
|
| 231 |
+
viewport_height = max(1, self.table_guides.viewport().height())
|
| 232 |
row_height = max(1, row_height) # Ensure non-zero
|
| 233 |
visible_rows = viewport_height // row_height
|
| 234 |
|
|
|
|
| 239 |
|
| 240 |
# Only update rows that aren't already loaded
|
| 241 |
for row in range(start_row, end_row):
|
| 242 |
+
if row < len(self._all_guides) and not self.table_guides.item(row, 0):
|
| 243 |
+
guide = self._all_guides[row]
|
| 244 |
|
| 245 |
# Create and set items efficiently
|
| 246 |
for col, value in enumerate([
|
| 247 |
+
guide['location'], guide['endonuclease'],
|
| 248 |
+
guide['sequence'], guide['strand'], guide['pam'],
|
| 249 |
+
guide['score'], "--.--"
|
| 250 |
]):
|
| 251 |
item = QTableWidgetItem(str(value))
|
| 252 |
item.setFlags(Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable)
|
| 253 |
+
self.table_guides.setItem(row, col, item)
|
| 254 |
|
| 255 |
+
if not self.table_guides.cellWidget(row, 7):
|
| 256 |
details_button = QtWidgets.QPushButton("Details")
|
| 257 |
+
self.table_guides.setCellWidget(row, 7, details_button)
|
| 258 |
|
| 259 |
except Exception as e:
|
| 260 |
self.logger.error(f"Error in _handle_scroll_virtual: {str(e)}")
|
| 261 |
|
| 262 |
+
def get_selected_guides(self):
|
| 263 |
+
"""Get selected guides with all necessary data"""
|
| 264 |
try:
|
| 265 |
+
selected_rows = sorted(set(item.row() for item in self.table_guides.selectedItems()))
|
| 266 |
+
selected_guides = []
|
| 267 |
|
| 268 |
# Get column indices once
|
| 269 |
columns = {
|
|
|
|
| 272 |
'sequence': 2,
|
| 273 |
'strand': 3,
|
| 274 |
'pam': 4,
|
| 275 |
+
'score': 5,
|
| 276 |
+
'off_target': 6
|
| 277 |
}
|
| 278 |
|
| 279 |
+
# Get current gene information from combo box
|
| 280 |
+
current_gene = self.combo_box_gene.currentText()
|
| 281 |
+
if ': ' in current_gene: # Format is "locus_tag: gene_name"
|
| 282 |
+
locus_tag, gene_name = current_gene.split(': ', 1)
|
| 283 |
+
else:
|
| 284 |
+
locus_tag = current_gene
|
| 285 |
+
gene_name = current_gene
|
| 286 |
+
|
| 287 |
+
for row in selected_rows:
|
| 288 |
+
# Create guide dictionary directly from table items
|
| 289 |
+
guide = {}
|
| 290 |
+
valid_row = True
|
| 291 |
+
|
| 292 |
+
for col_name, col_index in columns.items():
|
| 293 |
+
item = self.table_guides.item(row, col_index)
|
| 294 |
+
if item is None:
|
| 295 |
+
valid_row = False
|
| 296 |
+
self.logger.warning(f"Missing data in row {row}, column {col_name}")
|
| 297 |
+
break
|
| 298 |
+
guide[col_name] = item.text()
|
| 299 |
+
|
| 300 |
+
if valid_row:
|
| 301 |
+
# Add gene information
|
| 302 |
+
guide['locus_tag'] = locus_tag.strip()
|
| 303 |
+
guide['gene_name'] = gene_name.strip()
|
| 304 |
+
selected_guides.append(guide)
|
| 305 |
|
| 306 |
+
if not selected_guides:
|
| 307 |
+
self.logger.warning("No valid guides selected")
|
| 308 |
|
| 309 |
+
return selected_guides
|
| 310 |
|
| 311 |
except Exception as e:
|
| 312 |
+
self.logger.error(f"Error getting selected guides: {str(e)}")
|
|
|
|
| 313 |
return []
|
| 314 |
|
| 315 |
def get_row_data(self, row):
|
| 316 |
return {
|
| 317 |
+
'location': self.table_guides.item(row, 0).text(),
|
| 318 |
+
'endonuclease': self.table_guides.item(row, 1).text(),
|
| 319 |
+
'sequence': self.table_guides.item(row, 2).text(),
|
| 320 |
+
'strand': self.table_guides.item(row, 3).text(),
|
| 321 |
+
'pam': self.table_guides.item(row, 4).text(),
|
| 322 |
+
'score': self.table_guides.item(row, 5).text(),
|
| 323 |
+
'off_target': self.table_guides.item(row, 6).text()
|
| 324 |
}
|
| 325 |
|
| 326 |
def set_combo_box_endonuclease(self, endonucleases):
|
| 327 |
self.combo_box_endonuclease.addItems(endonucleases)
|
| 328 |
|
| 329 |
def set_combo_box_gene(self, genes):
|
|
|
|
| 330 |
try:
|
|
|
|
| 331 |
|
| 332 |
# Disable UI updates
|
| 333 |
self.combo_box_gene.blockSignals(True)
|
|
|
|
| 354 |
self.combo_box_gene.setUpdatesEnabled(True)
|
| 355 |
self.combo_box_gene.blockSignals(False)
|
| 356 |
|
|
|
|
|
|
|
|
|
|
| 357 |
except Exception as e:
|
| 358 |
self.logger.error(f"Error setting genes in combo box: {str(e)}")
|
| 359 |
self.logger.error(f"Stack trace: {traceback.format_exc()}")
|
|
|
|
| 380 |
doc.setHtml(sequence)
|
| 381 |
self.text_edit_gene_viewer.setDocument(doc)
|
| 382 |
|
| 383 |
+
def select_all_guides(self, select):
|
| 384 |
+
for row in range(self.table_guides.rowCount()):
|
| 385 |
+
self.table_guides.selectRow(row) if select else self.table_guides.clearSelection()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 386 |
|
| 387 |
def get_export_file_path(self):
|
| 388 |
# Implement this method to get the export file path from the user
|
|
|
|
| 394 |
self.logger.debug(f"Gene selection changed to: {selected_text}")
|
| 395 |
|
| 396 |
# Reset scroll position
|
| 397 |
+
self.table_guides.verticalScrollBar().setValue(0)
|
| 398 |
|
| 399 |
# Filter and display targets
|
| 400 |
if hasattr(self, '_complete_targets'):
|
| 401 |
+
self.display_guides_in_table(self._complete_targets)
|
| 402 |
|
| 403 |
# Emit signal for controller to update gene sequence
|
| 404 |
self.gene_selected.emit(selected_text)
|
|
|
|
| 410 |
def get_table_headers(self):
|
| 411 |
"""Get current table headers"""
|
| 412 |
headers = []
|
| 413 |
+
for i in range(self.table_guides.columnCount()):
|
| 414 |
+
headers.append(self.table_guides.horizontalHeaderItem(i).text())
|
| 415 |
return headers
|
| 416 |
|
| 417 |
def add_scoring_column(self, algorithm_name, position=None):
|
| 418 |
"""Add a new column for alternative scoring method at specified position"""
|
| 419 |
if position is None:
|
| 420 |
# Add to end if no position specified
|
| 421 |
+
position = self.table_guides.columnCount()
|
| 422 |
|
| 423 |
+
self.table_guides.insertColumn(position)
|
| 424 |
+
self.table_guides.setHorizontalHeaderItem(
|
| 425 |
position,
|
| 426 |
QtWidgets.QTableWidgetItem(algorithm_name)
|
| 427 |
)
|
| 428 |
|
| 429 |
# Shift any existing columns after the insertion point
|
| 430 |
+
for i in range(self.table_guides.columnCount() - 1, position, -1):
|
| 431 |
+
for row in range(self.table_guides.rowCount()):
|
| 432 |
+
self.table_guides.setItem(row, i, self.table_guides.takeItem(row, i-1))
|
| 433 |
|
| 434 |
# Move column header
|
| 435 |
+
header_item = self.table_guides.takeHorizontalHeaderItem(i-1)
|
| 436 |
if header_item:
|
| 437 |
+
self.table_guides.setHorizontalHeaderItem(i, header_item)
|
| 438 |
|
| 439 |
return position
|
| 440 |
+
|
| 441 |
+
def update_off_target_details(self, off_target_results, detailed_results=None):
|
| 442 |
+
"""Update off-target scores and details"""
|
| 443 |
+
try:
|
| 444 |
+
# Store detailed results if provided
|
| 445 |
+
if detailed_results:
|
| 446 |
+
self._off_target_details = detailed_results
|
| 447 |
+
|
| 448 |
+
# Update off-target scores in table
|
| 449 |
+
for row in range(self.table_guides.rowCount()):
|
| 450 |
+
sequence = self.table_guides.item(row, 2).text()
|
| 451 |
+
if sequence in off_target_results:
|
| 452 |
+
score = off_target_results[sequence]
|
| 453 |
+
score_item = QTableWidgetItem(str(score))
|
| 454 |
+
self.table_guides.setItem(row, 6, score_item)
|
| 455 |
+
|
| 456 |
+
# Add details button if detailed results exist
|
| 457 |
+
if detailed_results and sequence in detailed_results:
|
| 458 |
+
details_button = QtWidgets.QPushButton("Details")
|
| 459 |
+
details_button.clicked.connect(self._show_details)
|
| 460 |
+
self.table_guides.setCellWidget(row, 7, details_button)
|
| 461 |
+
|
| 462 |
+
self.table_guides.resizeColumnsToContents()
|
| 463 |
+
|
| 464 |
+
except Exception as e:
|
| 465 |
+
self.logger.error(f"Error updating off-target details: {str(e)}")
|
| 466 |
+
show_error(self.settings, "Error updating off-target details", str(e))
|
| 467 |
+
|
| 468 |
+
def _show_details(self):
|
| 469 |
+
"""Show off-target details dialog"""
|
| 470 |
+
try:
|
| 471 |
+
button = self.sender()
|
| 472 |
+
index = self.table_guides.indexAt(button.pos())
|
| 473 |
+
sequence = self.table_guides.item(index.row(), 2).text()
|
| 474 |
+
|
| 475 |
+
if sequence in self._off_target_details:
|
| 476 |
+
details = self._off_target_details[sequence]
|
| 477 |
+
|
| 478 |
+
msg = QtWidgets.QMessageBox()
|
| 479 |
+
msg.setWindowTitle("Details")
|
| 480 |
+
|
| 481 |
+
# Format details message
|
| 482 |
+
chromo_str = "<html><b>Reference gRNA:</b><br>Location, Sequence, Strand, PAM, On Score<br></html>"
|
| 483 |
+
input_str = (f"{self.table_guides.item(index.row(),0).text()}, {sequence}, "
|
| 484 |
+
f"{self.table_guides.item(index.row(),3).text()}, "
|
| 485 |
+
f"{self.table_guides.item(index.row(),4).text()}, "
|
| 486 |
+
f"{self.table_guides.item(index.row(),5).text()}<br><br>")
|
| 487 |
+
detail_str = "<html><b>Off-Target Hits:</b><br>Off Score, Chromosome, Location, Sequence<br></html>"
|
| 488 |
+
|
| 489 |
+
msg.setText(chromo_str + input_str + detail_str + "<br>".join(details))
|
| 490 |
+
msg.exec()
|
| 491 |
+
|
| 492 |
+
except Exception as e:
|
| 493 |
+
self.logger.error(f"Error showing details: {str(e)}")
|
| 494 |
+
show_error(self.settings, "Error showing details", str(e))
|